ngwr 14.1.0 → 14.3.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 (136) hide show
  1. package/README.md +23 -22
  2. package/command-palette/styles/_index.scss +17 -0
  3. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  4. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  5. package/fesm2022/ngwr-calendar.mjs +4 -2
  6. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  7. package/fesm2022/ngwr-cascader.mjs +2 -2
  8. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  9. package/fesm2022/ngwr-command-palette.mjs +2 -2
  10. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  11. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  12. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  13. package/fesm2022/ngwr-event-calendar.mjs +17 -5
  14. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  15. package/fesm2022/ngwr-form.mjs +30 -1
  16. package/fesm2022/ngwr-form.mjs.map +1 -1
  17. package/fesm2022/ngwr-i18n-ar.mjs +429 -0
  18. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -0
  19. package/fesm2022/ngwr-i18n-cs.mjs +399 -0
  20. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -0
  21. package/fesm2022/ngwr-i18n-de.mjs +418 -0
  22. package/fesm2022/ngwr-i18n-de.mjs.map +1 -0
  23. package/fesm2022/ngwr-i18n-en.mjs +23 -0
  24. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  25. package/fesm2022/ngwr-i18n-es.mjs +399 -0
  26. package/fesm2022/ngwr-i18n-es.mjs.map +1 -0
  27. package/fesm2022/ngwr-i18n-fr.mjs +423 -0
  28. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -0
  29. package/fesm2022/ngwr-i18n-he.mjs +422 -0
  30. package/fesm2022/ngwr-i18n-he.mjs.map +1 -0
  31. package/fesm2022/ngwr-i18n-hi.mjs +399 -0
  32. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -0
  33. package/fesm2022/ngwr-i18n-id.mjs +399 -0
  34. package/fesm2022/ngwr-i18n-id.mjs.map +1 -0
  35. package/fesm2022/ngwr-i18n-it.mjs +418 -0
  36. package/fesm2022/ngwr-i18n-it.mjs.map +1 -0
  37. package/fesm2022/ngwr-i18n-ja.mjs +403 -0
  38. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -0
  39. package/fesm2022/ngwr-i18n-ko.mjs +411 -0
  40. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -0
  41. package/fesm2022/ngwr-i18n-nl.mjs +415 -0
  42. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -0
  43. package/fesm2022/ngwr-i18n-pl.mjs +425 -0
  44. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -0
  45. package/fesm2022/ngwr-i18n-pt.mjs +408 -0
  46. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -0
  47. package/fesm2022/ngwr-i18n-ru.mjs +11 -0
  48. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  49. package/fesm2022/ngwr-i18n-sv.mjs +399 -0
  50. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -0
  51. package/fesm2022/ngwr-i18n-tr.mjs +423 -0
  52. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -0
  53. package/fesm2022/ngwr-i18n-uk.mjs +420 -0
  54. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -0
  55. package/fesm2022/ngwr-i18n-vi.mjs +416 -0
  56. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -0
  57. package/fesm2022/ngwr-i18n-zh-TW.mjs +407 -0
  58. package/fesm2022/ngwr-i18n-zh-TW.mjs.map +1 -0
  59. package/fesm2022/ngwr-i18n-zh.mjs +398 -0
  60. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -0
  61. package/fesm2022/ngwr-lightbox.mjs +2 -2
  62. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  63. package/fesm2022/ngwr-line-chart.mjs +24 -4
  64. package/fesm2022/ngwr-line-chart.mjs.map +1 -1
  65. package/fesm2022/ngwr-list.mjs +2 -2
  66. package/fesm2022/ngwr-list.mjs.map +1 -1
  67. package/fesm2022/ngwr-pipes.mjs +75 -10
  68. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  69. package/fesm2022/ngwr-schema-form.mjs +185 -0
  70. package/fesm2022/ngwr-schema-form.mjs.map +1 -0
  71. package/fesm2022/ngwr-select.mjs +2 -2
  72. package/fesm2022/ngwr-select.mjs.map +1 -1
  73. package/fesm2022/ngwr-validators.mjs +8 -1
  74. package/fesm2022/ngwr-validators.mjs.map +1 -1
  75. package/fesm2022/ngwr-version.mjs +1 -1
  76. package/fesm2022/ngwr-version.mjs.map +1 -1
  77. package/i18n/ar.json +389 -0
  78. package/i18n/cs.json +389 -0
  79. package/i18n/de.json +389 -0
  80. package/i18n/en.json +10 -2
  81. package/i18n/es.json +389 -0
  82. package/i18n/fr.json +389 -0
  83. package/i18n/he.json +389 -0
  84. package/i18n/hi.json +389 -0
  85. package/i18n/id.json +389 -0
  86. package/i18n/it.json +389 -0
  87. package/i18n/ja.json +389 -0
  88. package/i18n/ko.json +389 -0
  89. package/i18n/nl.json +389 -0
  90. package/i18n/pl.json +389 -0
  91. package/i18n/pt.json +389 -0
  92. package/i18n/ru.json +10 -2
  93. package/i18n/sv.json +389 -0
  94. package/i18n/tr.json +389 -0
  95. package/i18n/uk.json +389 -0
  96. package/i18n/vi.json +389 -0
  97. package/i18n/zh-TW.json +389 -0
  98. package/i18n/zh.json +389 -0
  99. package/llms-full.txt +87 -1
  100. package/llms.txt +1 -1
  101. package/package.json +89 -7
  102. package/schema-form/styles/_index.scss +15 -0
  103. package/schematics/migrations/v9/index.js +11 -3
  104. package/schematics/use/symbol-map.json +1 -0
  105. package/skills/ngwr/SKILL.md +1 -1
  106. package/skills/ngwr/references/catalog.md +21 -0
  107. package/styles.scss +1 -0
  108. package/theme/styles/_focus.scss +23 -2
  109. package/types/ngwr-calendar.d.ts +2 -0
  110. package/types/ngwr-command-palette.d.ts +1 -1
  111. package/types/ngwr-event-calendar.d.ts +5 -0
  112. package/types/ngwr-form.d.ts +88 -2
  113. package/types/ngwr-i18n-ar.d.ts +6 -0
  114. package/types/ngwr-i18n-cs.d.ts +6 -0
  115. package/types/ngwr-i18n-de.d.ts +6 -0
  116. package/types/ngwr-i18n-es.d.ts +6 -0
  117. package/types/ngwr-i18n-fr.d.ts +6 -0
  118. package/types/ngwr-i18n-he.d.ts +6 -0
  119. package/types/ngwr-i18n-hi.d.ts +6 -0
  120. package/types/ngwr-i18n-id.d.ts +6 -0
  121. package/types/ngwr-i18n-it.d.ts +6 -0
  122. package/types/ngwr-i18n-ja.d.ts +6 -0
  123. package/types/ngwr-i18n-ko.d.ts +6 -0
  124. package/types/ngwr-i18n-nl.d.ts +6 -0
  125. package/types/ngwr-i18n-pl.d.ts +6 -0
  126. package/types/ngwr-i18n-pt.d.ts +6 -0
  127. package/types/ngwr-i18n-sv.d.ts +6 -0
  128. package/types/ngwr-i18n-tr.d.ts +6 -0
  129. package/types/ngwr-i18n-uk.d.ts +6 -0
  130. package/types/ngwr-i18n-vi.d.ts +6 -0
  131. package/types/ngwr-i18n-zh-TW.d.ts +6 -0
  132. package/types/ngwr-i18n-zh.d.ts +6 -0
  133. package/types/ngwr-line-chart.d.ts +15 -0
  134. package/types/ngwr-pipes.d.ts +45 -4
  135. package/types/ngwr-schema-form.d.ts +90 -0
  136. package/types/ngwr-version.d.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-pipes.mjs","sources":["../../../projects/lib/pipes/bytes.ts","../../../projects/lib/pipes/date.ts","../../../projects/lib/pipes/mark.ts","../../../projects/lib/pipes/number.ts","../../../projects/lib/pipes/plural.ts","../../../projects/lib/pipes/range.ts","../../../projects/lib/pipes/truncate.ts","../../../projects/lib/pipes/ngwr-pipes.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 { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nconst UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] as const;\n\n/**\n * Humanise a byte count using binary (1024-based) units.\n *\n * @example\n * ```html\n * {{ 1234 | wrBytes }} <!-- \"1.2 KB\" -->\n * {{ 1234567 | wrBytes: 0 }} <!-- \"1 MB\" -->\n * {{ 0 | wrBytes }} <!-- \"0 B\" -->\n * ```\n */\n@Pipe({ name: 'wrBytes' })\nexport class WrBytes implements PipeTransform {\n transform(value: number | string | null | undefined, decimals = 1): string {\n if (value === null || value === undefined || value === '') return '';\n const n = Number(value);\n if (!Number.isFinite(n) || n <= 0) return '0 B';\n\n // Floored as well as capped: for `0 < n < 1` the log is negative, so an\n // unclamped index reads `UNITS[-1]` (`undefined`) and divides by `1024 ** -1`,\n // which rendered `0.5` as \"512.0 undefined\".\n const i = Math.min(Math.max(0, Math.floor(Math.log(n) / Math.log(1024))), UNITS.length - 1);\n const v = n / 1024 ** i;\n // Whole bytes never show a fractional part.\n const fractionDigits = i === 0 ? 0 : Math.max(0, decimals);\n return `${v.toFixed(fractionDigits)} ${UNITS[i]}`;\n }\n}\n","/**\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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport { type WrDateFormat, WrDateAdapter } from 'ngwr/date';\n\n/**\n * Locale-aware date formatting. Delegates to `WrDateAdapter` when\n * `provideWrDateAdapter()` is in the provider tree (giving access to named\n * format keys like `'shortDate'` and `'longDate'`), otherwise falls back to\n * a minimal `Intl.DateTimeFormat`-based formatter so the pipe still works\n * without the date-adapter package wired up.\n *\n * @example\n * ```html\n * {{ now | wrDate }} <!-- shortDate -->\n * {{ now | wrDate: 'mediumDateTime' }} <!-- named key -->\n * {{ now | wrDate: 'dd.MM.yyyy' }} <!-- raw token string -->\n * ```\n */\n@Pipe({ name: 'wrDate' })\nexport class WrDate implements PipeTransform {\n private readonly adapter = inject<WrDateAdapter<Date> | null>(WrDateAdapter, { optional: true });\n private readonly locale = inject(LOCALE_ID);\n\n transform(\n value: Date | string | number | null | undefined,\n format: WrDateFormat | (string & {}) = 'shortDate'\n ): string {\n if (value === null || value === undefined || value === '') return '';\n const date = value instanceof Date ? value : new Date(value);\n if (Number.isNaN(date.getTime())) return '';\n\n if (this.adapter) return this.adapter.format(date, format);\n return this.fallback(date, format);\n }\n\n /** Tiny fallback for the named keys when no adapter is provided. */\n private fallback(date: Date, format: WrDateFormat | (string & {})): string {\n const named: Record<string, Intl.DateTimeFormatOptions> = {\n shortDate: { dateStyle: 'short' },\n mediumDate: { dateStyle: 'medium' },\n longDate: { dateStyle: 'long' },\n time: { timeStyle: 'short' },\n shortDateTime: { dateStyle: 'short', timeStyle: 'short' },\n mediumDateTime: { dateStyle: 'medium', timeStyle: 'short' },\n };\n const opts = named[format] ?? { dateStyle: 'short' };\n return new Intl.DateTimeFormat(this.locale, opts).format(date);\n }\n}\n","/**\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 { Pipe, type PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, type SafeHtml } from '@angular/platform-browser';\n\n/** Escapes RegExp metacharacters so user input is safe to inline. @internal */\nconst RE_META = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** HTML-entities for the four chars that matter inside our `<mark>` wrap. @internal */\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\n/**\n * Wrap every occurrence of `query` in `<mark>` tags. Useful for highlighting\n * search-term matches in autocomplete results, lists, and table cells.\n *\n * The output is `SafeHtml` — bind with `[innerHTML]`. Input is escaped first\n * so user-supplied values can't inject markup.\n *\n * @example\n * ```html\n * <span [innerHTML]=\"row.name | wrMark: query()\"></span>\n *\n * <!-- Case-sensitive: -->\n * <span [innerHTML]=\"row.name | wrMark: query() : true\"></span>\n * ```\n */\n@Pipe({ name: 'wrMark' })\nexport class WrMark implements PipeTransform {\n private readonly sanitizer = inject(DomSanitizer);\n\n /**\n * @param value Source text.\n * @param query Search term to highlight. Empty/null skips the wrap.\n * @param caseSensitive Match exactly. @default false\n */\n transform(value: string | null | undefined, query: string | null | undefined, caseSensitive = false): SafeHtml {\n if (value === null || value === undefined) return '';\n const text = String(value);\n if (!query) return text;\n\n // Match the RAW text and escape the pieces afterwards, never the other way\n // round. Escaping first rewrites `&` as `&amp;`, and a query of `m` then lands\n // INSIDE that entity: the `<mark>` splits it and the browser paints the\n // entity's own letters, so `Tom & Jerry` renders as `Tom &amp; Jerry`. The\n // mirror failure is a match that can never happen — `<`, `>` and `\"` no longer\n // exist in escaped text. The pipe's promise is untouched: every piece that came\n // from `value` still goes through `escapeHtml`, matched or not, so the only\n // tags in the output are the `<mark>`s built here.\n const pattern = new RegExp(`(${query.replace(RE_META, '\\\\$&')})`, caseSensitive ? 'g' : 'gi');\n const html = text\n .split(pattern)\n .map((part, index) => (index % 2 ? `<mark>${escapeHtml(part)}</mark>` : escapeHtml(part)))\n .join('');\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n}\n","/**\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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrNumberStyle } from './interfaces';\n\n/**\n * Locale-aware number formatting via `Intl.NumberFormat`. Uses Angular's\n * `LOCALE_ID` as the default locale.\n *\n * Accepts either a style shortcut (with optional currency code) or a full\n * `Intl.NumberFormatOptions` object for fine-grained control.\n *\n * @example\n * ```html\n * {{ 1234.5 | wrNumber }} <!-- \"1,234.5\" -->\n * {{ 0.875 | wrNumber: 'percent' }} <!-- \"88%\" -->\n * {{ 19.99 | wrNumber: 'currency': 'USD' }} <!-- \"$19.99\" -->\n * {{ 19.99 | wrNumber: { minimumFractionDigits: 2 } }}\n * ```\n */\n@Pipe({ name: 'wrNumber' })\nexport class WrNumber implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(\n value: number | string | null | undefined,\n styleOrOptions: WrNumberStyle | Intl.NumberFormatOptions = 'decimal',\n currency = 'USD'\n ): string {\n if (value === null || value === undefined || value === '') return '';\n const n = Number(value);\n if (!Number.isFinite(n)) return '';\n\n const options: Intl.NumberFormatOptions =\n typeof styleOrOptions === 'string' ? this.toOptions(styleOrOptions, currency) : styleOrOptions;\n\n return new Intl.NumberFormat(this.locale, options).format(n);\n }\n\n private toOptions(style: WrNumberStyle, currency: string): Intl.NumberFormatOptions {\n switch (style) {\n case 'percent':\n return { style: 'percent', maximumFractionDigits: 2 };\n case 'currency':\n return { style: 'currency', currency };\n case 'decimal':\n default:\n return { style: 'decimal' };\n }\n }\n}\n\nexport type { WrNumberStyle } from './interfaces';\n","/**\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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrPluralForms, WrPluralOptions } from './interfaces';\n\n/**\n * Locale-aware pluralization via `Intl.PluralRules`. Picks the right word\n * form for the count — including languages with more than two forms\n * (Russian, Polish, Arabic, …) — without hand-rolled `n % 10` rules.\n *\n * Categories follow CLDR: `zero` / `one` / `two` / `few` / `many` /\n * `other`. Missing categories fall back to `other`.\n *\n * @example\n * ```html\n * {{ 1 | wrPlural: { one: 'comment', other: 'comments' } }}\n * <!-- \"1 comment\" -->\n *\n * {{ 5 | wrPlural: { one: 'файл', few: 'файла', other: 'файлов' } }}\n * <!-- \"5 файлов\" (with LOCALE_ID = ru) -->\n *\n * {{ count() | wrPlural: forms : { includeValue: false } }}\n * <!-- word only -->\n * ```\n */\n@Pipe({ name: 'wrPlural' })\nexport class WrPlural implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | null | undefined, forms: WrPluralForms, options: WrPluralOptions = {}): string {\n if (value === null || value === undefined || !Number.isFinite(value)) return '';\n const locale = options.locale ?? this.locale;\n const category = new Intl.PluralRules(locale).select(value);\n const word = forms[category] ?? forms.other;\n if (options.includeValue === false) return word;\n return `${new Intl.NumberFormat(locale).format(value)} ${word}`;\n }\n}\n\nexport type { WrPluralForms, WrPluralOptions } from './interfaces';\n","/**\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 { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Creates an array of sequential indices `[0, 1, 2, ..., n-1]`.\n *\n * Useful for repeating template blocks a fixed number of times.\n *\n * @example\n * ```html\n * @for (i of (5 | wrRange); track i) {\n * <div>Item {{ i }}</div>\n * }\n * ```\n */\n@Pipe({ name: 'wrRange' })\nexport class WrRange implements PipeTransform {\n transform(length: number): number[] {\n return Array.from({ length }, (_, i) => i);\n }\n}\n","/**\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 { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Clamp a string to `length` characters, appending `ellipsis` when truncated.\n *\n * @example\n * ```html\n * {{ 'Hello, world' | wrTruncate: 5 }} <!-- \"Hello…\" -->\n * {{ 'Hello, world' | wrTruncate: 8: '...' }} <!-- \"Hello, w...\" -->\n * ```\n */\n@Pipe({ name: 'wrTruncate' })\nexport class WrTruncate implements PipeTransform {\n transform(value: string | null | undefined, length = 80, ellipsis = '…'): string {\n if (value === null || value === undefined) return '';\n const s = String(value);\n if (s.length <= length) return s;\n return s.slice(0, Math.max(0, length)) + ellipsis;\n }\n}\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-pipes.mjs","sources":["../../../projects/lib/pipes/bytes.ts","../../../projects/lib/pipes/date.ts","../../../projects/lib/pipes/mark.ts","../../../projects/lib/pipes/number.ts","../../../projects/lib/pipes/plural.ts","../../../projects/lib/pipes/range.ts","../../../projects/lib/pipes/truncate.ts","../../../projects/lib/pipes/ngwr-pipes.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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Binary magnitudes, as the `Intl` unit that labels each one. The COUNTING is\n * 1024-based and these names are decimal — which is the same thing the old\n * hand-written `['B', 'KB', 'MB', …]` table did, and what almost every file\n * manager does. Strictly, 1024 bytes is a kibibyte; `Intl` has no unit for it.\n */\nconst UNITS = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte'] as const;\n\n/**\n * Humanise a byte count using binary (1024-based) units, in the reader's locale.\n *\n * @example\n * ```html\n * {{ 1234 | wrBytes }} <!-- en: \"1.2 kB\" de: \"1,2 kB\" ru: \"1,2 кБ\" -->\n * {{ 1234567 | wrBytes: 0 }} <!-- en: \"1 MB\" fr: \"1 Mo\" -->\n * {{ 0 | wrBytes }} <!-- en: \"0 B\" ru: \"0 Б\" -->\n * ```\n *\n * **Both halves come from `Intl`, and the unit table is not the catalog's.** The\n * number always did need `LOCALE_ID` — it went through `toFixed`, which writes a\n * full stop in every language, so a German page read `1.2 KB` beside its own\n * `1,2` everywhere else — and the three formatting pipes beside this one\n * (`wrDate`, `wrNumber`, `wrPlural`) had injected it since they shipped.\n *\n * The unit could not follow them into `ngwr/i18n`, and the reason is the pipe\n * rather than the strings: a PURE pipe is re-invoked only when its argument\n * changes, so a catalog that lands after the first render — which is every\n * `provideWrI18nHttpLoader` app — would never reach it, and the pipe would be\n * correct under the static loader and silently English under the other one.\n * `LOCALE_ID` is a constant, so `Intl` has no such failure mode, and it knows\n * every locale ICU ships rather than the twenty-two this package translates.\n *\n * One visible change in English and it is the only one: `KB` became `kB`, which\n * is what CLDR calls that unit. `B`, the space before it and every other\n * magnitude read exactly as they did — see `format` for the part of that which\n * is not free.\n *\n * The other consequence, kept here so nobody rediscovers it as a bug:\n * `<wr-file-upload>` prints its own sizes from `fileUpload.unitKb` and friends,\n * which are public catalog keys a consumer may have overridden, so it renders\n * `KB` where this pipe renders `kB`. Retiring those keys would silently break\n * every override that had been translating them, which is a major-version move.\n */\n@Pipe({ name: 'wrBytes' })\nexport class WrBytes implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | string | null | undefined, decimals = 1): string {\n if (value === null || value === undefined || value === '') return '';\n const n = Number(value);\n if (!Number.isFinite(n) || n <= 0) return this.format(0, 0, 0);\n\n // Floored as well as capped: for `0 < n < 1` the log is negative, so an\n // unclamped index reads `UNITS[-1]` (`undefined`) and divides by `1024 ** -1`,\n // which rendered `0.5` as \"512.0 undefined\".\n const i = Math.min(Math.max(0, Math.floor(Math.log(n) / Math.log(1024))), UNITS.length - 1);\n // Whole bytes never show a fractional part.\n return this.format(n / 1024 ** i, i, i === 0 ? 0 : Math.max(0, decimals));\n }\n\n /**\n * `Intl`'s NARROW unit, with one space put back between it and the number.\n *\n * Neither display mode is right on its own. `short` spells the smallest unit\n * as a word — \"512 byte\", \"0 byte\" in English, \"0 Byte\" in German — which is\n * both worse copy than the `B` this pipe has always rendered and a bigger\n * change than the fix is worth. `narrow` gives `B` / `Б` / `o` correctly and\n * then closes the gap: \"1.2kB\".\n *\n * So the parts are reassembled rather than the string. Going through\n * `formatToParts` is what keeps the ORDER the locale's own — Hebrew puts the\n * unit first, and hand-joining `${n} ${unit}` would silently straighten every\n * such locale into English order.\n */\n private format(value: number, unit: number, fractionDigits: number): string {\n const parts = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit: UNITS[unit],\n unitDisplay: 'narrow',\n minimumFractionDigits: fractionDigits,\n maximumFractionDigits: fractionDigits,\n }).formatToParts(value);\n\n const at = parts.findIndex(part => part.type === 'unit');\n if (at === -1) return parts.map(part => part.value).join('');\n\n const out = parts.map(part => part.value);\n // The slot on the number's side of the unit: already a separator in some\n // locales, absent in others.\n const gap = at === 0 ? 1 : at - 1;\n if (parts[gap]?.type === 'literal') out[gap] = ' ';\n else out.splice(at === 0 ? 1 : at, 0, ' ');\n return out.join('');\n }\n}\n","/**\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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport { type WrDateFormat, WrDateAdapter } from 'ngwr/date';\n\n/**\n * Locale-aware date formatting. Delegates to `WrDateAdapter` when\n * `provideWrDateAdapter()` is in the provider tree (giving access to named\n * format keys like `'shortDate'` and `'longDate'`), otherwise falls back to\n * a minimal `Intl.DateTimeFormat`-based formatter so the pipe still works\n * without the date-adapter package wired up.\n *\n * @example\n * ```html\n * {{ now | wrDate }} <!-- shortDate -->\n * {{ now | wrDate: 'mediumDateTime' }} <!-- named key -->\n * {{ now | wrDate: 'dd.MM.yyyy' }} <!-- raw token string -->\n * ```\n */\n@Pipe({ name: 'wrDate' })\nexport class WrDate implements PipeTransform {\n private readonly adapter = inject<WrDateAdapter<Date> | null>(WrDateAdapter, { optional: true });\n private readonly locale = inject(LOCALE_ID);\n\n transform(\n value: Date | string | number | null | undefined,\n format: WrDateFormat | (string & {}) = 'shortDate'\n ): string {\n if (value === null || value === undefined || value === '') return '';\n const date = value instanceof Date ? value : new Date(value);\n if (Number.isNaN(date.getTime())) return '';\n\n if (this.adapter) return this.adapter.format(date, format);\n return this.fallback(date, format);\n }\n\n /** Tiny fallback for the named keys when no adapter is provided. */\n private fallback(date: Date, format: WrDateFormat | (string & {})): string {\n const named: Record<string, Intl.DateTimeFormatOptions> = {\n shortDate: { dateStyle: 'short' },\n mediumDate: { dateStyle: 'medium' },\n longDate: { dateStyle: 'long' },\n time: { timeStyle: 'short' },\n shortDateTime: { dateStyle: 'short', timeStyle: 'short' },\n mediumDateTime: { dateStyle: 'medium', timeStyle: 'short' },\n };\n const opts = named[format] ?? { dateStyle: 'short' };\n return new Intl.DateTimeFormat(this.locale, opts).format(date);\n }\n}\n","/**\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 { Pipe, type PipeTransform, inject } from '@angular/core';\nimport { DomSanitizer, type SafeHtml } from '@angular/platform-browser';\n\n/** Escapes RegExp metacharacters so user input is safe to inline. @internal */\nconst RE_META = /[\\\\^$.*+?()[\\]{}|]/g;\n\n/** HTML-entities for the four chars that matter inside our `<mark>` wrap. @internal */\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n}\n\n/**\n * Wrap every occurrence of `query` in `<mark>` tags. Useful for highlighting\n * search-term matches in autocomplete results, lists, and table cells.\n *\n * The output is `SafeHtml` — bind with `[innerHTML]`. Input is escaped first\n * so user-supplied values can't inject markup.\n *\n * @example\n * ```html\n * <span [innerHTML]=\"row.name | wrMark: query()\"></span>\n *\n * <!-- Case-sensitive: -->\n * <span [innerHTML]=\"row.name | wrMark: query() : true\"></span>\n * ```\n */\n@Pipe({ name: 'wrMark' })\nexport class WrMark implements PipeTransform {\n private readonly sanitizer = inject(DomSanitizer);\n\n /**\n * @param value Source text.\n * @param query Search term to highlight. Empty/null skips the wrap.\n * @param caseSensitive Match exactly. @default false\n */\n transform(value: string | null | undefined, query: string | null | undefined, caseSensitive = false): SafeHtml {\n if (value === null || value === undefined) return '';\n const text = String(value);\n if (!query) return text;\n\n // Match the RAW text and escape the pieces afterwards, never the other way\n // round. Escaping first rewrites `&` as `&amp;`, and a query of `m` then lands\n // INSIDE that entity: the `<mark>` splits it and the browser paints the\n // entity's own letters, so `Tom & Jerry` renders as `Tom &amp; Jerry`. The\n // mirror failure is a match that can never happen — `<`, `>` and `\"` no longer\n // exist in escaped text. The pipe's promise is untouched: every piece that came\n // from `value` still goes through `escapeHtml`, matched or not, so the only\n // tags in the output are the `<mark>`s built here.\n const pattern = new RegExp(`(${query.replace(RE_META, '\\\\$&')})`, caseSensitive ? 'g' : 'gi');\n const html = text\n .split(pattern)\n .map((part, index) => (index % 2 ? `<mark>${escapeHtml(part)}</mark>` : escapeHtml(part)))\n .join('');\n\n return this.sanitizer.bypassSecurityTrustHtml(html);\n }\n}\n","/**\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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrNumberStyle } from './interfaces';\n\n/**\n * Locale-aware number formatting via `Intl.NumberFormat`. Uses Angular's\n * `LOCALE_ID` as the default locale.\n *\n * Accepts either a style shortcut (with optional currency code) or a full\n * `Intl.NumberFormatOptions` object for fine-grained control.\n *\n * @example\n * ```html\n * {{ 1234.5 | wrNumber }} <!-- \"1,234.5\" -->\n * {{ 0.875 | wrNumber: 'percent' }} <!-- \"88%\" -->\n * {{ 19.99 | wrNumber: 'currency': 'USD' }} <!-- \"$19.99\" -->\n * {{ 19.99 | wrNumber: { minimumFractionDigits: 2 } }}\n * ```\n */\n@Pipe({ name: 'wrNumber' })\nexport class WrNumber implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(\n value: number | string | null | undefined,\n styleOrOptions: WrNumberStyle | Intl.NumberFormatOptions = 'decimal',\n currency = 'USD'\n ): string {\n if (value === null || value === undefined || value === '') return '';\n const n = Number(value);\n if (!Number.isFinite(n)) return '';\n\n const options: Intl.NumberFormatOptions =\n typeof styleOrOptions === 'string' ? this.toOptions(styleOrOptions, currency) : styleOrOptions;\n\n return new Intl.NumberFormat(this.locale, options).format(n);\n }\n\n private toOptions(style: WrNumberStyle, currency: string): Intl.NumberFormatOptions {\n switch (style) {\n case 'percent':\n return { style: 'percent', maximumFractionDigits: 2 };\n case 'currency':\n return { style: 'currency', currency };\n case 'decimal':\n default:\n return { style: 'decimal' };\n }\n }\n}\n\nexport type { WrNumberStyle } from './interfaces';\n","/**\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 { LOCALE_ID, Pipe, inject } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\nimport type { WrPluralForms, WrPluralOptions } from './interfaces';\n\n/**\n * Locale-aware pluralization via `Intl.PluralRules`. Picks the right word\n * form for the count — including languages with more than two forms\n * (Russian, Polish, Arabic, …) — without hand-rolled `n % 10` rules.\n *\n * Categories follow CLDR: `zero` / `one` / `two` / `few` / `many` /\n * `other`. Missing categories fall back to `other`.\n *\n * @example\n * ```html\n * {{ 1 | wrPlural: { one: 'comment', other: 'comments' } }}\n * <!-- \"1 comment\" -->\n *\n * {{ 5 | wrPlural: { one: 'файл', few: 'файла', other: 'файлов' } }}\n * <!-- \"5 файлов\" (with LOCALE_ID = ru) -->\n *\n * {{ count() | wrPlural: forms : { includeValue: false } }}\n * <!-- word only -->\n * ```\n */\n@Pipe({ name: 'wrPlural' })\nexport class WrPlural implements PipeTransform {\n private readonly locale = inject(LOCALE_ID);\n\n transform(value: number | null | undefined, forms: WrPluralForms, options: WrPluralOptions = {}): string {\n if (value === null || value === undefined || !Number.isFinite(value)) return '';\n const locale = options.locale ?? this.locale;\n const category = new Intl.PluralRules(locale).select(value);\n const word = forms[category] ?? forms.other;\n if (options.includeValue === false) return word;\n return `${new Intl.NumberFormat(locale).format(value)} ${word}`;\n }\n}\n\nexport type { WrPluralForms, WrPluralOptions } from './interfaces';\n","/**\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 { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Creates an array of sequential indices `[0, 1, 2, ..., n-1]`.\n *\n * Useful for repeating template blocks a fixed number of times.\n *\n * @example\n * ```html\n * @for (i of (5 | wrRange); track i) {\n * <div>Item {{ i }}</div>\n * }\n * ```\n */\n@Pipe({ name: 'wrRange' })\nexport class WrRange implements PipeTransform {\n transform(length: number): number[] {\n return Array.from({ length }, (_, i) => i);\n }\n}\n","/**\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 { Pipe } from '@angular/core';\nimport type { PipeTransform } from '@angular/core';\n\n/**\n * Clamp a string to `length` characters, appending `ellipsis` when truncated.\n *\n * @example\n * ```html\n * {{ 'Hello, world' | wrTruncate: 5 }} <!-- \"Hello…\" -->\n * {{ 'Hello, world' | wrTruncate: 8: '...' }} <!-- \"Hello, w...\" -->\n * ```\n */\n@Pipe({ name: 'wrTruncate' })\nexport class WrTruncate implements PipeTransform {\n transform(value: string | null | undefined, length = 80, ellipsis = '…'): string {\n if (value === null || value === undefined) return '';\n const s = String(value);\n if (s.length <= length) return s;\n return s.slice(0, Math.max(0, length)) + ellipsis;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, isDevMode, ViewEncapsulation, Component } from '@angular/core';
3
+ import { isFieldTree, FormField } from '@angular/forms/signals';
4
+ import { WrCheckbox } from 'ngwr/checkbox';
5
+ import { WrDatePicker } from 'ngwr/date-picker';
6
+ import { WR_FIELD, wrFieldLabel, WrFormField } from 'ngwr/form';
7
+ import { WrInput } from 'ngwr/input';
8
+ import { WrInputNumber } from 'ngwr/input-number';
9
+ import { WrRadio, WrRadioGroup } from 'ngwr/radio';
10
+ import { WrOption, WrSelect } from 'ngwr/select';
11
+ import { WrSlider } from 'ngwr/slider';
12
+ import { WrSwitch } from 'ngwr/switch';
13
+ import { WrTextarea } from 'ngwr/textarea';
14
+ import { numAttr } from 'ngwr/utils';
15
+
16
+ /**
17
+ * @license
18
+ *
19
+ * Use of this source code is governed by an MIT-style license that can be
20
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
21
+ */
22
+ /**
23
+ * The two kinds that carry their own label. Everything else gets one above the
24
+ * control, from `<wr-form-field>`.
25
+ */
26
+ const SELF_LABELLING = new Set(['checkbox', 'switch']);
27
+ /** What each kind writes into the model, for the dev-mode mismatch warning. */
28
+ const EXPECTED_TYPE = {
29
+ input: 'string',
30
+ textarea: 'string',
31
+ number: 'number',
32
+ checkbox: 'boolean',
33
+ switch: 'boolean',
34
+ slider: 'number',
35
+ };
36
+ /**
37
+ * Warn when a `kind` contradicts what the field actually holds.
38
+ *
39
+ * This is the one failure mode of the whole component that nothing else
40
+ * reports: `kind` is a string on a metadata value, so TypeScript cannot hold it
41
+ * against the field's own type, and a `kind: 'number'` on a `string` field
42
+ * renders a `<wr-input-number>` that writes numbers into it forever. The runtime
43
+ * value is the only evidence available, so an empty or nullish one says nothing
44
+ * and is left alone.
45
+ */
46
+ function warnOnKindMismatch(f) {
47
+ const expected = EXPECTED_TYPE[f.spec.kind];
48
+ const value = f.field().value();
49
+ if (!expected || value === null || value === undefined || value === '')
50
+ return;
51
+ if (typeof value === expected)
52
+ return;
53
+ // eslint-disable-next-line no-console -- dev-mode validation
54
+ console.warn(`[NGWR] <wr-schema-form>: field "${f.key}" is described as kind '${f.spec.kind}', ` +
55
+ `which writes a ${expected}, but the field holds a ${typeof value}. ` +
56
+ `The control will overwrite the model with the wrong type.`);
57
+ }
58
+ /**
59
+ * A form drawn from a Signal Forms schema — one `<wr-form-field>`, one control
60
+ * and one set of error messages per described field, laid out on a grid.
61
+ *
62
+ * The presentation rides on the SAME schema as the validation, through the
63
+ * `WR_FIELD` metadata key from `ngwr/form`:
64
+ *
65
+ * ```ts
66
+ * const profile = form(model, path => {
67
+ * required(path.email);
68
+ * email(path.email);
69
+ * metadata(path.email, WR_FIELD, () => ({ kind: 'input', type: 'email' }));
70
+ * metadata(path.role, WR_FIELD, () => ({ kind: 'select', options: ROLES }));
71
+ * });
72
+ * ```
73
+ * ```html
74
+ * <wr-schema-form [field]="profile" [columns]="2" />
75
+ * ```
76
+ *
77
+ * **A field with no `WR_FIELD` metadata is skipped**, silently and on purpose: a
78
+ * schema may describe more than one screen renders, and guessing a control from
79
+ * a field's type would draw a `wr-switch` for every boolean flag in the model.
80
+ * Order is the model's own key order.
81
+ *
82
+ * **`required`, `disabled`, `readonly` and `hidden` are NOT in the spec.** They
83
+ * are Angular's own schema functions, `[formField]` already applies the first
84
+ * three to the control, and this component reads `hidden()` to decide whether to
85
+ * draw the field at all — which Angular's docs say a template has to do itself.
86
+ * Re-declaring any of them here would be a second source of truth for state the
87
+ * form already owns.
88
+ *
89
+ * **Why its own entry point rather than `ngwr/form`.** Drawing nine kinds means
90
+ * depending on nine controls, and `ngwr/form` is the layout primitive every
91
+ * form imports — folding this in would put a date picker and a slider into the
92
+ * bundle of an app that only wanted `<wr-form-field>`.
93
+ *
94
+ * @see https://ngwr.dev/reference/components/schema-form
95
+ */
96
+ class WrSchemaForm {
97
+ // Generic, rather than `FieldTree<Record<string, unknown>>`, because a model
98
+ // declared as an `interface` has no index signature and is not assignable to
99
+ // that: every consumer would have had to declare their model as a type alias
100
+ // just to bind it. `T extends object` still refuses a field tree over a
101
+ // primitive, which has no subfields and would draw nothing.
102
+ /**
103
+ * The form to draw — the value `form()` returned, or any object-valued field
104
+ * inside one.
105
+ */
106
+ field = input.required(/* @ts-ignore */
107
+ ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
108
+ /**
109
+ * Grid columns. A field spans one of them unless its spec says otherwise.
110
+ * @default 1
111
+ */
112
+ columns = input(1, { ...(ngDevMode ? { debugName: "columns" } : /* istanbul ignore next */ {}), transform: numAttr(1) });
113
+ /**
114
+ * The track list, written as a style rather than published as a
115
+ * `--wr-schema-form-columns` hook: `columns` always has a value, so the
116
+ * inline style would win over any declaration a consumer wrote and the hook
117
+ * would be one nobody could use.
118
+ */
119
+ template = computed(() => `repeat(${Math.max(1, this.columns())}, minmax(0, 1fr))`, /* @ts-ignore */
120
+ ...(ngDevMode ? [{ debugName: "template" }] : /* istanbul ignore next */ []));
121
+ /**
122
+ * The described fields, in the model's key order.
123
+ *
124
+ * A field tree over an object is iterable as `[key, field]` pairs, which is
125
+ * the whole reason this component needs no field list of its own. `isFieldTree`
126
+ * guards the pair: a model carrying a method or a non-field property would
127
+ * otherwise be read as a field and answer `undefined` to everything.
128
+ */
129
+ fields = computed(() => {
130
+ const columns = this.columns();
131
+ const out = [];
132
+ for (const [key, child] of this.field()) {
133
+ if (!isFieldTree(child))
134
+ continue;
135
+ const field = child;
136
+ const spec = field().metadata(WR_FIELD)?.();
137
+ if (!spec)
138
+ continue;
139
+ const label = spec.label ?? wrFieldLabel(key);
140
+ const span = Math.min(Math.max(Math.round(spec.span ?? 1), 1), columns);
141
+ out.push({
142
+ key,
143
+ field,
144
+ spec,
145
+ label,
146
+ labelAbove: SELF_LABELLING.has(spec.kind) ? '' : label,
147
+ column: `span ${span}`,
148
+ });
149
+ }
150
+ if (isDevMode())
151
+ out.forEach(f => warnOnKindMismatch(f));
152
+ return out;
153
+ }, /* @ts-ignore */
154
+ ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
155
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrSchemaForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
156
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: WrSchemaForm, isStandalone: true, selector: "wr-schema-form", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-columns": "template()" }, classAttribute: "wr-schema-form" }, ngImport: i0, template: "@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "component", type: WrDatePicker, selector: "wr-date-picker", inputs: ["mode", "format", "placeholder", "min", "max", "dateFilter", "timeFormat", "showSeconds", "step", "disabled", "readonly", "value", "ariaLabel", "panelAriaLabel"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrFormField, selector: "wr-form-field", inputs: ["label", "hint", "required", "optional", "optionalLabel", "controlId", "autoErrors"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputNumber, selector: "wr-input-number", inputs: ["min", "max", "step", "decimals", "showSteppers", "size", "rounded", "prefix", "suffix", "placeholder", "ariaLabel", "incrementLabel", "decrementLabel", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }, { kind: "component", type: WrRadio, selector: "wr-radio", inputs: ["id", "value", "icon", "size", "disabled"] }, { kind: "component", type: WrRadioGroup, selector: "wr-radio-group", inputs: ["name", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrSelect, selector: "wr-select", inputs: ["placeholder", "clearLabel", "ariaLabel", "disabled", "readonly", "rounded", "size", "responsive", "mode", "searchable", "searchQuery", "options", "displayWith", "loader", "serverSearch", "debounceMs", "minChars", "freeText", "virtualScroll", "rowHeight", "viewportHeight", "overscan", "noResultsText", "loadingText", "loading", "separators", "allowDuplicates", "validate", "clearable", "maxItems", "maxTagCount", "value"], outputs: ["searchQueryChange", "searchChange", "valueChange", "touch"] }, { kind: "component", type: WrSlider, selector: "wr-slider", inputs: ["min", "max", "step", "range", "disabled", "readonly", "showLabel", "ariaLabel", "upperLabel", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrSwitch, selector: "wr-switch", inputs: ["ariaLabel", "id", "disabled", "readonly", "size", "checked"], outputs: ["checkedChange", "touch"] }, { kind: "component", type: WrTextarea, selector: "wr-textarea", inputs: ["placeholder", "ariaLabel", "size", "rows", "resizable", "resize", "readonly", "autosize", "maxRows", "disabled", "value"], outputs: ["valueChange", "touch"] }], encapsulation: i0.ViewEncapsulation.None });
157
+ }
158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrSchemaForm, decorators: [{
159
+ type: Component,
160
+ args: [{ selector: 'wr-schema-form', encapsulation: ViewEncapsulation.None, imports: [
161
+ FormField,
162
+ WrCheckbox,
163
+ WrDatePicker,
164
+ WrFormField,
165
+ WrInput,
166
+ WrInputNumber,
167
+ WrOption,
168
+ WrRadio,
169
+ WrRadioGroup,
170
+ WrSelect,
171
+ WrSlider,
172
+ WrSwitch,
173
+ WrTextarea,
174
+ ], host: {
175
+ class: 'wr-schema-form',
176
+ '[style.grid-template-columns]': 'template()',
177
+ }, template: "@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n" }]
178
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }] } });
179
+
180
+ /**
181
+ * Generated bundle index. Do not edit.
182
+ */
183
+
184
+ export { WrSchemaForm };
185
+ //# sourceMappingURL=ngwr-schema-form.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngwr-schema-form.mjs","sources":["../../../projects/lib/schema-form/schema-form.ts","../../../projects/lib/schema-form/schema-form.html","../../../projects/lib/schema-form/ngwr-schema-form.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 { Component, ViewEncapsulation, computed, input, isDevMode } from '@angular/core';\nimport { FormField, type Field, type FieldTree, isFieldTree } from '@angular/forms/signals';\n\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WrDatePicker } from 'ngwr/date-picker';\nimport { WR_FIELD, WrFormField, wrFieldLabel, type WrFieldSpec } from 'ngwr/form';\nimport { WrInput } from 'ngwr/input';\nimport { WrInputNumber } from 'ngwr/input-number';\nimport { WrRadio, WrRadioGroup } from 'ngwr/radio';\nimport { WrOption, WrSelect } from 'ngwr/select';\nimport { WrSlider } from 'ngwr/slider';\nimport { WrSwitch } from 'ngwr/switch';\nimport { WrTextarea } from 'ngwr/textarea';\nimport { numAttr } from 'ngwr/utils';\n\n/** One field the template draws, resolved once per schema change. */\ninterface WrRenderedField {\n readonly key: string;\n readonly field: Field<never>;\n readonly spec: WrFieldSpec;\n /** `spec.label`, or the key de-camel-cased. */\n readonly label: string;\n /**\n * The same text, or `''` for the two kinds that carry their own label inside\n * the control. Resolved here rather than in the template: a method call in a\n * template is re-run on every pass, and this is decided once per schema.\n */\n readonly labelAbove: string;\n /** `grid-column`, already clamped to the form's own `columns`. */\n readonly column: string;\n}\n\n/**\n * The two kinds that carry their own label. Everything else gets one above the\n * control, from `<wr-form-field>`.\n */\nconst SELF_LABELLING = new Set<WrFieldSpec['kind']>(['checkbox', 'switch']);\n\n/** What each kind writes into the model, for the dev-mode mismatch warning. */\nconst EXPECTED_TYPE: Partial<Record<WrFieldSpec['kind'], string>> = {\n input: 'string',\n textarea: 'string',\n number: 'number',\n checkbox: 'boolean',\n switch: 'boolean',\n slider: 'number',\n};\n\n/**\n * Warn when a `kind` contradicts what the field actually holds.\n *\n * This is the one failure mode of the whole component that nothing else\n * reports: `kind` is a string on a metadata value, so TypeScript cannot hold it\n * against the field's own type, and a `kind: 'number'` on a `string` field\n * renders a `<wr-input-number>` that writes numbers into it forever. The runtime\n * value is the only evidence available, so an empty or nullish one says nothing\n * and is left alone.\n */\nfunction warnOnKindMismatch(f: WrRenderedField): void {\n const expected = EXPECTED_TYPE[f.spec.kind];\n const value = f.field().value();\n if (!expected || value === null || value === undefined || value === '') return;\n if (typeof value === expected) return;\n\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] <wr-schema-form>: field \"${f.key}\" is described as kind '${f.spec.kind}', ` +\n `which writes a ${expected}, but the field holds a ${typeof value}. ` +\n `The control will overwrite the model with the wrong type.`\n );\n}\n\n/**\n * A form drawn from a Signal Forms schema — one `<wr-form-field>`, one control\n * and one set of error messages per described field, laid out on a grid.\n *\n * The presentation rides on the SAME schema as the validation, through the\n * `WR_FIELD` metadata key from `ngwr/form`:\n *\n * ```ts\n * const profile = form(model, path => {\n * required(path.email);\n * email(path.email);\n * metadata(path.email, WR_FIELD, () => ({ kind: 'input', type: 'email' }));\n * metadata(path.role, WR_FIELD, () => ({ kind: 'select', options: ROLES }));\n * });\n * ```\n * ```html\n * <wr-schema-form [field]=\"profile\" [columns]=\"2\" />\n * ```\n *\n * **A field with no `WR_FIELD` metadata is skipped**, silently and on purpose: a\n * schema may describe more than one screen renders, and guessing a control from\n * a field's type would draw a `wr-switch` for every boolean flag in the model.\n * Order is the model's own key order.\n *\n * **`required`, `disabled`, `readonly` and `hidden` are NOT in the spec.** They\n * are Angular's own schema functions, `[formField]` already applies the first\n * three to the control, and this component reads `hidden()` to decide whether to\n * draw the field at all — which Angular's docs say a template has to do itself.\n * Re-declaring any of them here would be a second source of truth for state the\n * form already owns.\n *\n * **Why its own entry point rather than `ngwr/form`.** Drawing nine kinds means\n * depending on nine controls, and `ngwr/form` is the layout primitive every\n * form imports — folding this in would put a date picker and a slider into the\n * bundle of an app that only wanted `<wr-form-field>`.\n *\n * @see https://ngwr.dev/reference/components/schema-form\n */\n@Component({\n selector: 'wr-schema-form',\n templateUrl: './schema-form.html',\n encapsulation: ViewEncapsulation.None,\n imports: [\n FormField,\n WrCheckbox,\n WrDatePicker,\n WrFormField,\n WrInput,\n WrInputNumber,\n WrOption,\n WrRadio,\n WrRadioGroup,\n WrSelect,\n WrSlider,\n WrSwitch,\n WrTextarea,\n ],\n host: {\n class: 'wr-schema-form',\n '[style.grid-template-columns]': 'template()',\n },\n})\nexport class WrSchemaForm<T extends object> {\n // Generic, rather than `FieldTree<Record<string, unknown>>`, because a model\n // declared as an `interface` has no index signature and is not assignable to\n // that: every consumer would have had to declare their model as a type alias\n // just to bind it. `T extends object` still refuses a field tree over a\n // primitive, which has no subfields and would draw nothing.\n /**\n * The form to draw — the value `form()` returned, or any object-valued field\n * inside one.\n */\n readonly field = input.required<FieldTree<T>>();\n\n /**\n * Grid columns. A field spans one of them unless its spec says otherwise.\n * @default 1\n */\n readonly columns = input(1, { transform: numAttr(1) });\n\n /**\n * The track list, written as a style rather than published as a\n * `--wr-schema-form-columns` hook: `columns` always has a value, so the\n * inline style would win over any declaration a consumer wrote and the hook\n * would be one nobody could use.\n */\n protected readonly template = computed(() => `repeat(${Math.max(1, this.columns())}, minmax(0, 1fr))`);\n\n /**\n * The described fields, in the model's key order.\n *\n * A field tree over an object is iterable as `[key, field]` pairs, which is\n * the whole reason this component needs no field list of its own. `isFieldTree`\n * guards the pair: a model carrying a method or a non-field property would\n * otherwise be read as a field and answer `undefined` to everything.\n */\n protected readonly fields = computed<readonly WrRenderedField[]>(() => {\n const columns = this.columns();\n const out: WrRenderedField[] = [];\n\n for (const [key, child] of this.field() as unknown as Iterable<[string, unknown]>) {\n if (!isFieldTree(child)) continue;\n\n const field = child as Field<never>;\n const spec = field().metadata(WR_FIELD)?.();\n if (!spec) continue;\n\n const label = spec.label ?? wrFieldLabel(key);\n const span = Math.min(Math.max(Math.round(spec.span ?? 1), 1), columns);\n out.push({\n key,\n field,\n spec,\n label,\n labelAbove: SELF_LABELLING.has(spec.kind) ? '' : label,\n column: `span ${span}`,\n });\n }\n\n if (isDevMode()) out.forEach(f => warnOnKindMismatch(f));\n return out;\n });\n}\n","@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1790,7 +1790,7 @@ class WrSelect {
1790
1790
  // eslint-disable-next-line @angular-eslint/no-forward-ref
1791
1791
  useExisting: forwardRef(() => WrSelect),
1792
1792
  },
1793
- ], queries: [{ propertyName: "projectedOptions", predicate: WrOption, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagInputEl", first: true, predicate: ["tagInput"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <!-- `id` / `aria-describedby` / `aria-invalid` come from a surrounding\n `<wr-form-field>` and are null without one \u2014 see `controlId` in the\n component. They go on the tab stop, the only labelable element here.\n `aria-label` is the trigger's name and belongs on the same element for\n the same reason: this input carries `role=\"combobox\"`, and the four\n other trigger shapes all bind `resolvedAriaLabel()`. Without it a\n search select with no placeholder rendered a combobox with no name at\n all \u2014 the one shape where the documented `ariaLabel` input reached\n nothing. -->\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [readonly]=\"isReadonly()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"onSearchBlur()\"\n />\n <!-- Pointer-only, and the `keydown.enter` / `keydown.space` pair this used to\n carry was dead code rather than the keyboard path it looked like: the \u00D7\n is `tabindex=\"-1\"` with no roving owner, so no key event ever originates\n here to bubble into a handler. It stays unfocusable because the same\n span sits inside the BUTTON trigger further down, where a focusable\n child is `nested-interactive` \u2014 the defect the window taskbar's close\n button already taught this repo. The keyboard reaches the function\n instead: Backspace on the empty field, which `onSearchKey` routes to\n `clearAll` for a single and to the last chip for a multi. -->\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__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 </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n <!-- At `maxItems` this input goes READONLY, never disabled: it is the only\n tab stop in the whole control (the chip \u00D7 and the clear button are\n `tabindex=\"-1\"` spans), so disabling it left a full tag select with\n nothing focusable at all \u2014 and Backspace on an empty draft, the one\n keyboard way back UNDER the cap, is bound to this very element. It was\n never needed as enforcement either: `tryAddTag` already refuses past\n capacity. -->\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"isReadonly() || (isTag() && atCapacity())\"\n [attr.aria-readonly]=\"hasChipSearch() && isReadonly() ? true : null\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__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}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"resolvedSize() === 'sm'\"\n [class.wr-select-panel--lg]=\"resolvedSize() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-busy]=\"isLoading() ? 'true' : null\"\n >\n <!-- `aria-busy` belongs on the listbox, not on this row: it is the container\n whose required `option` children are temporarily missing, and a global\n ARIA attribute on a role-less child makes that child an owned element the\n listbox may not own (axe `aria-required-children`, critical). Plain text\n inside the listbox is fine \u2014 only ELEMENTS are checked. -->\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n <!-- A listbox may only own `option` and `group` children, and as a bare `<div>`\n this row was the only thing in the tree when it showed \u2014 an\n `aria-required-children` critical. It cannot move out of the listbox the\n way the command palette's does: this element IS the listbox, and the id\n the trigger's `aria-controls` names is what gives it its accessible name.\n So it announces as a disabled option instead. Keyboard navigation skips\n it for free \u2014 arrow keys walk the registered `wr-option`s, and nothing\n registers this row. -->\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\" role=\"option\" aria-selected=\"false\" aria-disabled=\"true\">\n {{ resolvedNoResults() }}\n </div>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
1793
+ ], queries: [{ propertyName: "projectedOptions", predicate: WrOption, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagInputEl", first: true, predicate: ["tagInput"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <!-- `id` / `aria-describedby` / `aria-invalid` come from a surrounding\n `<wr-form-field>` and are null without one \u2014 see `controlId` in the\n component. They go on the tab stop, the only labelable element here.\n `aria-label` is the trigger's name and belongs on the same element for\n the same reason: this input carries `role=\"combobox\"`, and the four\n other trigger shapes all bind `resolvedAriaLabel()`. Without it a\n search select with no placeholder rendered a combobox with no name at\n all \u2014 the one shape where the documented `ariaLabel` input reached\n nothing. -->\n <!-- `aria-autocomplete` is static, and `list` rather than `both`: the panel\n is the only place a suggestion appears, nothing is written back into\n the field for the user. It describes a permanent CAPABILITY, which is\n why it does not track `open()` \u2014 the same reasoning `wr-mention`\n records at its own host binding. -->\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [readonly]=\"isReadonly()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"onSearchBlur()\"\n />\n <!-- Pointer-only, and the `keydown.enter` / `keydown.space` pair this used to\n carry was dead code rather than the keyboard path it looked like: the \u00D7\n is `tabindex=\"-1\"` with no roving owner, so no key event ever originates\n here to bubble into a handler. It stays unfocusable because the same\n span sits inside the BUTTON trigger further down, where a focusable\n child is `nested-interactive` \u2014 the defect the window taskbar's close\n button already taught this repo. The keyboard reaches the function\n instead: Backspace on the empty field, which `onSearchKey` routes to\n `clearAll` for a single and to the last chip for a multi. -->\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__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 </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n <!-- At `maxItems` this input goes READONLY, never disabled: it is the only\n tab stop in the whole control (the chip \u00D7 and the clear button are\n `tabindex=\"-1\"` spans), so disabling it left a full tag select with\n nothing focusable at all \u2014 and Backspace on an empty draft, the one\n keyboard way back UNDER the cap, is bound to this very element. It was\n never needed as enforcement either: `tryAddTag` already refuses past\n capacity. -->\n <!-- `aria-autocomplete` is conditional here, unlike the search input's:\n this field is only a combobox when a panel backs it. In plain tag\n mode there is no popup to suggest from, and claiming otherwise\n describes a control that does not exist. -->\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"isReadonly() || (isTag() && atCapacity())\"\n [attr.aria-readonly]=\"hasChipSearch() && isReadonly() ? true : null\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-autocomplete]=\"hasChipSearch() ? 'list' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__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}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"resolvedSize() === 'sm'\"\n [class.wr-select-panel--lg]=\"resolvedSize() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-busy]=\"isLoading() ? 'true' : null\"\n >\n <!-- `aria-busy` belongs on the listbox, not on this row: it is the container\n whose required `option` children are temporarily missing, and a global\n ARIA attribute on a role-less child makes that child an owned element the\n listbox may not own (axe `aria-required-children`, critical). Plain text\n inside the listbox is fine \u2014 only ELEMENTS are checked. -->\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n <!-- A listbox may only own `option` and `group` children, and as a bare `<div>`\n this row was the only thing in the tree when it showed \u2014 an\n `aria-required-children` critical. It cannot move out of the listbox the\n way the command palette's does: this element IS the listbox, and the id\n the trigger's `aria-controls` names is what gives it its accessible name.\n So it announces as a disabled option instead. Keyboard navigation skips\n it for free \u2014 arrow keys walk the registered `wr-option`s, and nothing\n registers this row. -->\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\" role=\"option\" aria-selected=\"false\" aria-disabled=\"true\">\n {{ resolvedNoResults() }}\n </div>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
1794
1794
  }
1795
1795
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrSelect, decorators: [{
1796
1796
  type: Component,
@@ -1800,7 +1800,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1800
1800
  // eslint-disable-next-line @angular-eslint/no-forward-ref
1801
1801
  useExisting: forwardRef(() => WrSelect),
1802
1802
  },
1803
- ], template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <!-- `id` / `aria-describedby` / `aria-invalid` come from a surrounding\n `<wr-form-field>` and are null without one \u2014 see `controlId` in the\n component. They go on the tab stop, the only labelable element here.\n `aria-label` is the trigger's name and belongs on the same element for\n the same reason: this input carries `role=\"combobox\"`, and the four\n other trigger shapes all bind `resolvedAriaLabel()`. Without it a\n search select with no placeholder rendered a combobox with no name at\n all \u2014 the one shape where the documented `ariaLabel` input reached\n nothing. -->\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [readonly]=\"isReadonly()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"onSearchBlur()\"\n />\n <!-- Pointer-only, and the `keydown.enter` / `keydown.space` pair this used to\n carry was dead code rather than the keyboard path it looked like: the \u00D7\n is `tabindex=\"-1\"` with no roving owner, so no key event ever originates\n here to bubble into a handler. It stays unfocusable because the same\n span sits inside the BUTTON trigger further down, where a focusable\n child is `nested-interactive` \u2014 the defect the window taskbar's close\n button already taught this repo. The keyboard reaches the function\n instead: Backspace on the empty field, which `onSearchKey` routes to\n `clearAll` for a single and to the last chip for a multi. -->\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__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 </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n <!-- At `maxItems` this input goes READONLY, never disabled: it is the only\n tab stop in the whole control (the chip \u00D7 and the clear button are\n `tabindex=\"-1\"` spans), so disabling it left a full tag select with\n nothing focusable at all \u2014 and Backspace on an empty draft, the one\n keyboard way back UNDER the cap, is bound to this very element. It was\n never needed as enforcement either: `tryAddTag` already refuses past\n capacity. -->\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"isReadonly() || (isTag() && atCapacity())\"\n [attr.aria-readonly]=\"hasChipSearch() && isReadonly() ? true : null\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__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}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"resolvedSize() === 'sm'\"\n [class.wr-select-panel--lg]=\"resolvedSize() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-busy]=\"isLoading() ? 'true' : null\"\n >\n <!-- `aria-busy` belongs on the listbox, not on this row: it is the container\n whose required `option` children are temporarily missing, and a global\n ARIA attribute on a role-less child makes that child an owned element the\n listbox may not own (axe `aria-required-children`, critical). Plain text\n inside the listbox is fine \u2014 only ELEMENTS are checked. -->\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n <!-- A listbox may only own `option` and `group` children, and as a bare `<div>`\n this row was the only thing in the tree when it showed \u2014 an\n `aria-required-children` critical. It cannot move out of the listbox the\n way the command palette's does: this element IS the listbox, and the id\n the trigger's `aria-controls` names is what gives it its accessible name.\n So it announces as a disabled option instead. Keyboard navigation skips\n it for free \u2014 arrow keys walk the registered `wr-option`s, and nothing\n registers this row. -->\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\" role=\"option\" aria-selected=\"false\" aria-disabled=\"true\">\n {{ resolvedNoResults() }}\n </div>\n }\n </div>\n</ng-template>\n" }]
1803
+ ], template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <!-- `id` / `aria-describedby` / `aria-invalid` come from a surrounding\n `<wr-form-field>` and are null without one \u2014 see `controlId` in the\n component. They go on the tab stop, the only labelable element here.\n `aria-label` is the trigger's name and belongs on the same element for\n the same reason: this input carries `role=\"combobox\"`, and the four\n other trigger shapes all bind `resolvedAriaLabel()`. Without it a\n search select with no placeholder rendered a combobox with no name at\n all \u2014 the one shape where the documented `ariaLabel` input reached\n nothing. -->\n <!-- `aria-autocomplete` is static, and `list` rather than `both`: the panel\n is the only place a suggestion appears, nothing is written back into\n the field for the user. It describes a permanent CAPABILITY, which is\n why it does not track `open()` \u2014 the same reasoning `wr-mention`\n records at its own host binding. -->\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n [readonly]=\"isReadonly()\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"onSearchBlur()\"\n />\n <!-- Pointer-only, and the `keydown.enter` / `keydown.space` pair this used to\n carry was dead code rather than the keyboard path it looked like: the \u00D7\n is `tabindex=\"-1\"` with no roving owner, so no key event ever originates\n here to bubble into a handler. It stays unfocusable because the same\n span sits inside the BUTTON trigger further down, where a focusable\n child is `nested-interactive` \u2014 the defect the window taskbar's close\n button already taught this repo. The keyboard reaches the function\n instead: Backspace on the empty field, which `onSearchKey` routes to\n `clearAll` for a single and to the last chip for a multi. -->\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__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 </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n <!-- At `maxItems` this input goes READONLY, never disabled: it is the only\n tab stop in the whole control (the chip \u00D7 and the clear button are\n `tabindex=\"-1\"` spans), so disabling it left a full tag select with\n nothing focusable at all \u2014 and Backspace on an empty draft, the one\n keyboard way back UNDER the cap, is bound to this very element. It was\n never needed as enforcement either: `tryAddTag` already refuses past\n capacity. -->\n <!-- `aria-autocomplete` is conditional here, unlike the search input's:\n this field is only a combobox when a panel backs it. In plain tag\n mode there is no popup to suggest from, and claiming otherwise\n describes a control that does not exist. -->\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"isReadonly() || (isTag() && atCapacity())\"\n [attr.aria-readonly]=\"hasChipSearch() && isReadonly() ? true : null\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-autocomplete]=\"hasChipSearch() ? 'list' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [attr.aria-readonly]=\"isReadonly() || null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n @if (!isReadonly()) {\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled() && !isReadonly()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__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}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"resolvedSize() === 'sm'\"\n [class.wr-select-panel--lg]=\"resolvedSize() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-busy]=\"isLoading() ? 'true' : null\"\n >\n <!-- `aria-busy` belongs on the listbox, not on this row: it is the container\n whose required `option` children are temporarily missing, and a global\n ARIA attribute on a role-less child makes that child an owned element the\n listbox may not own (axe `aria-required-children`, critical). Plain text\n inside the listbox is fine \u2014 only ELEMENTS are checked. -->\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n <!-- A listbox may only own `option` and `group` children, and as a bare `<div>`\n this row was the only thing in the tree when it showed \u2014 an\n `aria-required-children` critical. It cannot move out of the listbox the\n way the command palette's does: this element IS the listbox, and the id\n the trigger's `aria-controls` names is what gives it its accessible name.\n So it announces as a disabled option instead. Keyboard navigation skips\n it for free \u2014 arrow keys walk the registered `wr-option`s, and nothing\n registers this row. -->\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\" role=\"option\" aria-selected=\"false\" aria-disabled=\"true\">\n {{ resolvedNoResults() }}\n </div>\n }\n </div>\n</ng-template>\n" }]
1804
1804
  }], ctorParameters: () => [], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }, { type: i0.Output, args: ["searchQueryChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayWith", required: false }] }], loader: [{ type: i0.Input, args: [{ isSignal: true, alias: "loader", required: false }] }], serverSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "serverSearch", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], freeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "freeText", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], maxItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxItems", required: false }] }], maxTagCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTagCount", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], projectedOptions: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WrOption), { ...{ descendants: true }, isSignal: true }] }], panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { ...{ read: TemplateRef }, isSignal: true }] }], tagInputEl: [{ type: i0.ViewChild, args: ['tagInput', { isSignal: true }] }], searchInputEl: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
1805
1805
 
1806
1806
  /**