@skra/ui 0.2.0 → 0.3.1

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 (90) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +7 -7
  3. package/README.md +39 -39
  4. package/consumer-guide/README.md +84 -84
  5. package/consumer-guide/forms-and-binding.md +19 -3
  6. package/consumer-guide/layout.md +19 -0
  7. package/consumer-guide/overlays.md +100 -100
  8. package/consumer-guide/setup-and-gotchas.md +165 -103
  9. package/consumer-guide/theming-and-encapsulation.md +129 -115
  10. package/fesm2022/skra-ui-accordion.mjs +61 -43
  11. package/fesm2022/skra-ui-accordion.mjs.map +1 -1
  12. package/fesm2022/skra-ui-alert.mjs +26 -18
  13. package/fesm2022/skra-ui-alert.mjs.map +1 -1
  14. package/fesm2022/skra-ui-avatar.mjs +12 -8
  15. package/fesm2022/skra-ui-avatar.mjs.map +1 -1
  16. package/fesm2022/skra-ui-badge.mjs +19 -13
  17. package/fesm2022/skra-ui-badge.mjs.map +1 -1
  18. package/fesm2022/skra-ui-button.mjs +28 -19
  19. package/fesm2022/skra-ui-button.mjs.map +1 -1
  20. package/fesm2022/skra-ui-checkbox.mjs +3 -3
  21. package/fesm2022/skra-ui-checkbox.mjs.map +1 -1
  22. package/fesm2022/skra-ui-chip.mjs +15 -11
  23. package/fesm2022/skra-ui-chip.mjs.map +1 -1
  24. package/fesm2022/skra-ui-combobox.mjs +135 -130
  25. package/fesm2022/skra-ui-combobox.mjs.map +1 -1
  26. package/fesm2022/skra-ui-core.mjs +95 -73
  27. package/fesm2022/skra-ui-core.mjs.map +1 -1
  28. package/fesm2022/skra-ui-counter.mjs +9 -6
  29. package/fesm2022/skra-ui-counter.mjs.map +1 -1
  30. package/fesm2022/skra-ui-datepicker.mjs +334 -307
  31. package/fesm2022/skra-ui-datepicker.mjs.map +1 -1
  32. package/fesm2022/skra-ui-dialog.mjs +5 -4
  33. package/fesm2022/skra-ui-dialog.mjs.map +1 -1
  34. package/fesm2022/skra-ui-field.mjs +65 -43
  35. package/fesm2022/skra-ui-field.mjs.map +1 -1
  36. package/fesm2022/skra-ui-icon.mjs +5 -4
  37. package/fesm2022/skra-ui-icon.mjs.map +1 -1
  38. package/fesm2022/skra-ui-input.mjs +53 -12
  39. package/fesm2022/skra-ui-input.mjs.map +1 -1
  40. package/fesm2022/skra-ui-kbd.mjs +3 -3
  41. package/fesm2022/skra-ui-kbd.mjs.map +1 -1
  42. package/fesm2022/skra-ui-layout.mjs +33 -21
  43. package/fesm2022/skra-ui-layout.mjs.map +1 -1
  44. package/fesm2022/skra-ui-link.mjs +3 -3
  45. package/fesm2022/skra-ui-link.mjs.map +1 -1
  46. package/fesm2022/skra-ui-loading-indicator.mjs +3 -3
  47. package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -1
  48. package/fesm2022/skra-ui-menu.mjs +8 -7
  49. package/fesm2022/skra-ui-menu.mjs.map +1 -1
  50. package/fesm2022/skra-ui-multi-select.mjs +102 -98
  51. package/fesm2022/skra-ui-multi-select.mjs.map +1 -1
  52. package/fesm2022/skra-ui-native-select.mjs +35 -17
  53. package/fesm2022/skra-ui-native-select.mjs.map +1 -1
  54. package/fesm2022/skra-ui-popover.mjs +5 -4
  55. package/fesm2022/skra-ui-popover.mjs.map +1 -1
  56. package/fesm2022/skra-ui-progress.mjs +7 -5
  57. package/fesm2022/skra-ui-progress.mjs.map +1 -1
  58. package/fesm2022/skra-ui-radio.mjs +3 -3
  59. package/fesm2022/skra-ui-radio.mjs.map +1 -1
  60. package/fesm2022/skra-ui-rating-display.mjs +14 -9
  61. package/fesm2022/skra-ui-rating-display.mjs.map +1 -1
  62. package/fesm2022/skra-ui-select.mjs +98 -95
  63. package/fesm2022/skra-ui-select.mjs.map +1 -1
  64. package/fesm2022/skra-ui-skeleton.mjs +16 -13
  65. package/fesm2022/skra-ui-skeleton.mjs.map +1 -1
  66. package/fesm2022/skra-ui-star-rating.mjs +18 -11
  67. package/fesm2022/skra-ui-star-rating.mjs.map +1 -1
  68. package/fesm2022/skra-ui-status-dot.mjs +7 -5
  69. package/fesm2022/skra-ui-status-dot.mjs.map +1 -1
  70. package/fesm2022/skra-ui-switch.mjs +3 -3
  71. package/fesm2022/skra-ui-switch.mjs.map +1 -1
  72. package/fesm2022/skra-ui-tabs.mjs +43 -29
  73. package/fesm2022/skra-ui-tabs.mjs.map +1 -1
  74. package/fesm2022/skra-ui-textarea.mjs +41 -15
  75. package/fesm2022/skra-ui-textarea.mjs.map +1 -1
  76. package/fesm2022/skra-ui-toast.mjs +35 -23
  77. package/fesm2022/skra-ui-toast.mjs.map +1 -1
  78. package/fesm2022/skra-ui-tooltip.mjs +12 -9
  79. package/fesm2022/skra-ui-tooltip.mjs.map +1 -1
  80. package/fesm2022/skra-ui-typography.mjs +9 -6
  81. package/fesm2022/skra-ui-typography.mjs.map +1 -1
  82. package/package.json +6 -6
  83. package/types/skra-ui-field.d.ts +3 -3
  84. package/types/skra-ui-field.d.ts.map +1 -1
  85. package/types/skra-ui-input.d.ts +24 -1
  86. package/types/skra-ui-input.d.ts.map +1 -1
  87. package/types/skra-ui-native-select.d.ts +9 -1
  88. package/types/skra-ui-native-select.d.ts.map +1 -1
  89. package/types/skra-ui-textarea.d.ts +10 -1
  90. package/types/skra-ui-textarea.d.ts.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-progress.mjs","sources":["../../../../libs/ui/progress/src/lib/progress/progress.ts","../../../../libs/ui/progress/src/skra-ui-progress.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\n/**\n * `progress[skraProgress]` — a determinate, horizontal task-completion bar (Wave 2,\n * `libs/ui/indicators`). Shows the known progress of a finite task (file upload, wizard\n * \"step 3 of 5\", import job) as a fill from 0 → `value` / `max`.\n *\n * SRP-distinct from `[skraLoadingIndicator]` (ADR 028): that primitive is the\n * *indeterminate* \"host is busy\" spinner injected into another component's `loading()`\n * slot; SkraProgress is the *determinate* \"task is N% complete\" standalone bar. The two\n * are complementary — see `adr/specs/component-progress-spec.md` § SRP boundary.\n *\n * **Why a Component on the native `<progress>` host** (ADR 040), not a `<div role=\"progressbar\">`:\n * 1. ADR 021 (CSP): the fill width is a continuous `value/max` length. A custom element would\n * force `[style.--progress-value]` (forbidden) or a class/`data-*`-per-percent hack. Native\n * `<progress value max>` lets the BROWSER compute the fill width on its\n * `::-webkit-progress-value` / `::-moz-progress-bar` pseudo — zero author inline-style.\n * 2. Free a11y: the native host carries `role=\"progressbar\"` + `aria-valuenow`/`-valuemin`/\n * `-valuemax` with no author ARIA (native-attr-first, boundary #9).\n *\n * Non-interactive: `<progress>` is non-focusable, so the State Surface is all-N/A (no\n * `disabled`/`loading`/`severity` in MVP — those are deferred / belong to other atoms).\n *\n * Naming (ADR 033): the consumer associates a `<label for>` / `aria-labelledby` (passed through\n * to the host). No `SkraFieldContext` injection (a progress bar is a read-only display, not a\n * form field); no `ariaLabel` input.\n *\n * `value`/`max` are plain consumer-driven inputs (ADR 037 — agnostic; wire the source from any\n * signal/form strategy). Native `<progress>` clamps `value` to `[0, max]`, so no author clamp.\n */\n@Component({\n selector: 'progress[skraProgress]',\n standalone: true,\n template: '',\n styleUrl: './progress.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // The data contract — reflected to the NATIVE attributes. The browser derives\n // role=\"progressbar\" + aria-valuenow/-valuemin/-valuemax + the fill width from these.\n // No [style.*] (ADR 021 — the fill is native, never an inline style) and no data-skra-*\n // (State Surface all-N/A).\n '[attr.value]': 'value()',\n '[attr.max]': 'max()',\n },\n})\nexport class SkraProgress {\n /** Current completion, reflected to the native `value` attribute (native clamps to [0, max]). */\n readonly value = input<number>(0);\n\n /** Upper bound, reflected to the native `max` attribute. Drives native `aria-valuemax`. */\n readonly max = input<number>(100);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BG;MAiBU,YAAY,CAAA;;AAEd,IAAA,KAAK,GAAG,KAAK,CAAS,CAAC,4EAAC;;AAGxB,IAAA,GAAG,GAAG,KAAK,CAAS,GAAG,0EAAC;uGALtB,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,gZAbb,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,+7BAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAaD,YAAY,EAAA,UAAA,EAAA,CAAA;kBAhBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,UAAA,EACtB,IAAI,EAAA,QAAA,EACN,EAAE,EAAA,eAAA,EAEK,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;;;;AAKJ,wBAAA,cAAc,EAAE,SAAS;AACzB,wBAAA,YAAY,EAAE,OAAO;AACtB,qBAAA,EAAA,MAAA,EAAA,CAAA,+7BAAA,CAAA,EAAA;;;ACjDH;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-progress.mjs","sources":["../../../../libs/ui/progress/src/lib/progress/progress.ts","../../../../libs/ui/progress/src/skra-ui-progress.ts"],"sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ViewEncapsulation,\r\n input,\r\n} from '@angular/core';\r\n\r\n/**\r\n * `progress[skraProgress]` — a determinate, horizontal task-completion bar (Wave 2,\r\n * `libs/ui/indicators`). Shows the known progress of a finite task (file upload, wizard\r\n * \"step 3 of 5\", import job) as a fill from 0 → `value` / `max`.\r\n *\r\n * SRP-distinct from `[skraLoadingIndicator]` (ADR 028): that primitive is the\r\n * *indeterminate* \"host is busy\" spinner injected into another component's `loading()`\r\n * slot; SkraProgress is the *determinate* \"task is N% complete\" standalone bar. The two\r\n * are complementary — see `adr/specs/component-progress-spec.md` § SRP boundary.\r\n *\r\n * **Why a Component on the native `<progress>` host** (ADR 040), not a `<div role=\"progressbar\">`:\r\n * 1. ADR 021 (CSP): the fill width is a continuous `value/max` length. A custom element would\r\n * force `[style.--progress-value]` (forbidden) or a class/`data-*`-per-percent hack. Native\r\n * `<progress value max>` lets the BROWSER compute the fill width on its\r\n * `::-webkit-progress-value` / `::-moz-progress-bar` pseudo — zero author inline-style.\r\n * 2. Free a11y: the native host carries `role=\"progressbar\"` + `aria-valuenow`/`-valuemin`/\r\n * `-valuemax` with no author ARIA (native-attr-first, boundary #9).\r\n *\r\n * Non-interactive: `<progress>` is non-focusable, so the State Surface is all-N/A (no\r\n * `disabled`/`loading`/`severity` in MVP — those are deferred / belong to other atoms).\r\n *\r\n * Naming (ADR 033): the consumer associates a `<label for>` / `aria-labelledby` (passed through\r\n * to the host). No `SkraFieldContext` injection (a progress bar is a read-only display, not a\r\n * form field); no `ariaLabel` input.\r\n *\r\n * `value`/`max` are plain consumer-driven inputs (ADR 037 — agnostic; wire the source from any\r\n * signal/form strategy). Native `<progress>` clamps `value` to `[0, max]`, so no author clamp.\r\n */\r\n@Component({\r\n selector: 'progress[skraProgress]',\r\n standalone: true,\r\n template: '',\r\n styleUrl: './progress.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n host: {\r\n // The data contract — reflected to the NATIVE attributes. The browser derives\r\n // role=\"progressbar\" + aria-valuenow/-valuemin/-valuemax + the fill width from these.\r\n // No [style.*] (ADR 021 — the fill is native, never an inline style) and no data-skra-*\r\n // (State Surface all-N/A).\r\n '[attr.value]': 'value()',\r\n '[attr.max]': 'max()',\r\n },\r\n})\r\nexport class SkraProgress {\r\n /** Current completion, reflected to the native `value` attribute (native clamps to [0, max]). */\r\n readonly value = input<number>(0);\r\n\r\n /** Upper bound, reflected to the native `max` attribute. Drives native `aria-valuemax`. */\r\n readonly max = input<number>(100);\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BG;MAiBU,YAAY,CAAA;;IAEd,KAAK,GAAG,KAAK,CAAS,CAAC;8EAAC;;IAGxB,GAAG,GAAG,KAAK,CAAS,GAAG;4EAAC;uGALtB,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,gZAbb,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,+7BAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAaD,YAAY,EAAA,UAAA,EAAA,CAAA;kBAhBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,UAAA,EACtB,IAAI,EAAA,QAAA,EACN,EAAE,EAAA,eAAA,EAEK,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;;;;AAKJ,wBAAA,cAAc,EAAE,SAAS;AACzB,wBAAA,YAAY,EAAE,OAAO;AACtB,qBAAA,EAAA,MAAA,EAAA,CAAA,+7BAAA,CAAA,EAAA;;;ACjDH;;AAEG;;;;"}
@@ -65,10 +65,10 @@ class SkraRadio {
65
65
  event.preventDefault();
66
66
  }
67
67
  }
68
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraRadio, deps: [], target: i0.ɵɵFactoryTarget.Component });
69
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: SkraRadio, isStandalone: true, selector: "input[type=\"radio\"][skraRadio]", host: { listeners: { "click": "blockWhenReadonly($event)", "keydown": "blockKeyWhenReadonly($event)" }, properties: { "attr.data-skra-disabled": "context?.isDisabled() ? \"\" : null", "attr.aria-invalid": "context?.hasError() ? \"true\" : null", "attr.aria-readonly": "context?.isReadonly() ? \"true\" : null", "attr.aria-describedby": "context?.describedByIds() ?? null" } }, ngImport: i0, template: '', isInline: true, styles: [":host{appearance:none;flex-shrink:0;cursor:pointer;outline:none;background-repeat:no-repeat;background-position:center;inline-size:var(--skra-comp-field-radio-size);block-size:var(--skra-comp-field-radio-size);border-width:var(--skra-comp-field-radio-border-width);border-style:solid;border-color:var(--skra-comp-field-radio-border-color);border-radius:50%;background-color:var(--skra-comp-field-radio-background);transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}:host(:checked){background-color:var(--skra-comp-field-radio-checked-background);border-color:var(--skra-comp-field-radio-checked-background);background-image:radial-gradient(circle,var(--skra-comp-field-radio-dot-color) 0,var(--skra-comp-field-radio-dot-color) calc(var(--skra-comp-field-radio-dot-size) / 2),transparent calc(var(--skra-comp-field-radio-dot-size) / 2 + .5px))}:host(:hover:not(:disabled):not([aria-readonly=true])){border-color:var(--skra-comp-field-radio-border-color-hover)}:host(:checked:hover:not(:disabled):not([aria-readonly=true])){background-color:var(--skra-comp-field-radio-checked-background-hover);border-color:var(--skra-comp-field-radio-checked-background-hover)}:host(:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled),:host([data-skra-disabled]){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([aria-readonly=true]){cursor:default;background-color:var(--skra-comp-field-background-readonly);border-color:var(--skra-comp-field-border-color-readonly)}:host(:checked[aria-readonly=true]){background-color:var(--skra-comp-field-radio-checked-background-readonly);border-color:var(--skra-comp-field-radio-checked-background-readonly)}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
68
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraRadio, deps: [], target: i0.ɵɵFactoryTarget.Component });
69
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: SkraRadio, isStandalone: true, selector: "input[type=\"radio\"][skraRadio]", host: { listeners: { "click": "blockWhenReadonly($event)", "keydown": "blockKeyWhenReadonly($event)" }, properties: { "attr.data-skra-disabled": "context?.isDisabled() ? \"\" : null", "attr.aria-invalid": "context?.hasError() ? \"true\" : null", "attr.aria-readonly": "context?.isReadonly() ? \"true\" : null", "attr.aria-describedby": "context?.describedByIds() ?? null" } }, ngImport: i0, template: '', isInline: true, styles: [":host{appearance:none;flex-shrink:0;cursor:pointer;outline:none;background-repeat:no-repeat;background-position:center;inline-size:var(--skra-comp-field-radio-size);block-size:var(--skra-comp-field-radio-size);border-width:var(--skra-comp-field-radio-border-width);border-style:solid;border-color:var(--skra-comp-field-radio-border-color);border-radius:50%;background-color:var(--skra-comp-field-radio-background);transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}:host(:checked){background-color:var(--skra-comp-field-radio-checked-background);border-color:var(--skra-comp-field-radio-checked-background);background-image:radial-gradient(circle,var(--skra-comp-field-radio-dot-color) 0,var(--skra-comp-field-radio-dot-color) calc(var(--skra-comp-field-radio-dot-size) / 2),transparent calc(var(--skra-comp-field-radio-dot-size) / 2 + .5px))}:host(:hover:not(:disabled):not([aria-readonly=true])){border-color:var(--skra-comp-field-radio-border-color-hover)}:host(:checked:hover:not(:disabled):not([aria-readonly=true])){background-color:var(--skra-comp-field-radio-checked-background-hover);border-color:var(--skra-comp-field-radio-checked-background-hover)}:host(:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled),:host([data-skra-disabled]){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([aria-readonly=true]){cursor:default;background-color:var(--skra-comp-field-background-readonly);border-color:var(--skra-comp-field-border-color-readonly)}:host(:checked[aria-readonly=true]){background-color:var(--skra-comp-field-radio-checked-background-readonly);border-color:var(--skra-comp-field-radio-checked-background-readonly)}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
70
70
  }
71
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraRadio, decorators: [{
71
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraRadio, decorators: [{
72
72
  type: Component,
73
73
  args: [{ selector: 'input[type="radio"][skraRadio]', standalone: true, template: '', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
74
74
  '[attr.data-skra-disabled]': 'context?.isDisabled() ? "" : null',
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-radio.mjs","sources":["../../../../libs/ui/radio/src/lib/radio/radio.ts","../../../../libs/ui/radio/src/skra-ui-radio.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n ViewEncapsulation,\n effect,\n inject,\n} from '@angular/core';\nimport { SkraFieldContext } from '@skra/ui/field';\n\n/**\n * Wires a native `<input type=\"radio\">` to SkraFieldContext when inside\n * `<skra-field>`, or stands alone with no cascade. Per ADR 039/040, Forge ships\n * no `<skra-radio>` / `<skra-radio-group>` wrappers — this is a Component-on-\n * the-native-host (`<input>` is void → `template: ''`, ADR 040 §2) carrying its\n * own `radio.scss` via Emulated encapsulation. Radios are\n * grouped via native `<fieldset skraFieldset>` + `<legend skraLegend>`,\n * coordination happens through the shared `name` attribute (HTML-native), and\n * one-of-N value selection is consumer-owned per ADR 037.\n *\n * <fieldset skraFieldset>\n * <legend skraLegend>Choose plan</legend>\n * <skra-field>\n * <input type=\"radio\" skraRadio name=\"plan\" value=\"free\"\n * [checked]=\"plan() === 'free'\" (change)=\"plan.set('free')\" />\n * <label skraLabelFor>Free</label>\n * </skra-field>\n * <skra-field>\n * <input type=\"radio\" skraRadio name=\"plan\" value=\"pro\"\n * [checked]=\"plan() === 'pro'\" (change)=\"plan.set('pro')\" />\n * <label skraLabelFor>Pro</label>\n * </skra-field>\n * </fieldset>\n *\n * Toggle state (`checked`) is NOT reflected as `data-skra-*` — slice CSS uses\n * the native `:checked` pseudo-class. Browser handles `aria-checked` natively.\n */\n@Component({\n selector: 'input[type=\"radio\"][skraRadio]',\n standalone: true,\n // Void host — no <ng-content>. See ADR 040 §2.\n template: '',\n styleUrl: './radio.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n '[attr.data-skra-disabled]': 'context?.isDisabled() ? \"\" : null',\n '[attr.aria-invalid]': 'context?.hasError() ? \"true\" : null',\n // Readonly is ARIA-only (native `readonly` doesn't apply to radios per HTML\n // spec). No `data-skra-readonly` — dead surface (styling keys off\n // `[aria-readonly]`), removed 2026-05-31 to match select's Q-3 ARIA-only\n // pattern (Batch 5 cleanroom review).\n '[attr.aria-readonly]': 'context?.isReadonly() ? \"true\" : null',\n '[attr.aria-describedby]': 'context?.describedByIds() ?? null',\n '(click)': 'blockWhenReadonly($event)',\n '(keydown)': 'blockKeyWhenReadonly($event)',\n },\n})\nexport class SkraRadio {\n private readonly el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n readonly context = inject(SkraFieldContext, { optional: true });\n\n constructor() {\n if (this.context) {\n const ctx = this.context;\n const el = this.el.nativeElement;\n effect(() => {\n el.id = ctx.fieldId();\n });\n effect(() => {\n el.disabled = ctx.isDisabled();\n });\n }\n }\n\n /**\n * Readonly enforcement — native radio has no `[readonly]`, so without this the\n * `aria-readonly` we announce would be a lie (the user could still change the\n * selection). `preventDefault` on `click` blocks the selection change from both\n * pointer and Space/Enter (keyboard activation dispatches a click on form\n * controls). Mirrors the `<select skraSelect>` readonly guard.\n */\n protected blockWhenReadonly(event: Event): void {\n if (this.context?.isReadonly()) event.preventDefault();\n }\n\n /**\n * Arrow keys move selection between radios in a `name` group WITHOUT a click,\n * so they need a separate guard. Block every key except `Tab` (focus traversal)\n * when readonly.\n */\n protected blockKeyWhenReadonly(event: KeyboardEvent): void {\n if (this.context?.isReadonly() && event.key !== 'Tab') {\n event.preventDefault();\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BG;MAsBU,SAAS,CAAA;AACH,IAAA,EAAE,GAAG,MAAM,CAA+B,UAAU,CAAC;IAC7D,OAAO,GAAG,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAE/D,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO;AACxB,YAAA,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;YAChC,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE;AACvB,YAAA,CAAC,CAAC;YACF,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE;AAChC,YAAA,CAAC,CAAC;QACJ;IACF;AAEA;;;;;;AAMG;AACO,IAAA,iBAAiB,CAAC,KAAY,EAAA;AACtC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE;YAAE,KAAK,CAAC,cAAc,EAAE;IACxD;AAEA;;;;AAIG;AACO,IAAA,oBAAoB,CAAC,KAAoB,EAAA;AACjD,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE;YACrD,KAAK,CAAC,cAAc,EAAE;QACxB;IACF;uGArCW,SAAS,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAT,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,SAAS,qdAjBV,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,mrEAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAiBD,SAAS,EAAA,UAAA,EAAA,CAAA;kBArBrB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gCAAgC,EAAA,UAAA,EAC9B,IAAI,EAAA,QAAA,EAEN,EAAE,EAAA,eAAA,EAEK,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;AACJ,wBAAA,2BAA2B,EAAE,mCAAmC;AAChE,wBAAA,qBAAqB,EAAE,qCAAqC;;;;;AAK5D,wBAAA,sBAAsB,EAAE,uCAAuC;AAC/D,wBAAA,yBAAyB,EAAE,mCAAmC;AAC9D,wBAAA,SAAS,EAAE,2BAA2B;AACtC,wBAAA,WAAW,EAAE,8BAA8B;AAC5C,qBAAA,EAAA,MAAA,EAAA,CAAA,mrEAAA,CAAA,EAAA;;;ACxDH;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-radio.mjs","sources":["../../../../libs/ui/radio/src/lib/radio/radio.ts","../../../../libs/ui/radio/src/skra-ui-radio.ts"],"sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n ViewEncapsulation,\r\n effect,\r\n inject,\r\n} from '@angular/core';\r\nimport { SkraFieldContext } from '@skra/ui/field';\r\n\r\n/**\r\n * Wires a native `<input type=\"radio\">` to SkraFieldContext when inside\r\n * `<skra-field>`, or stands alone with no cascade. Per ADR 039/040, Forge ships\r\n * no `<skra-radio>` / `<skra-radio-group>` wrappers — this is a Component-on-\r\n * the-native-host (`<input>` is void → `template: ''`, ADR 040 §2) carrying its\r\n * own `radio.scss` via Emulated encapsulation. Radios are\r\n * grouped via native `<fieldset skraFieldset>` + `<legend skraLegend>`,\r\n * coordination happens through the shared `name` attribute (HTML-native), and\r\n * one-of-N value selection is consumer-owned per ADR 037.\r\n *\r\n * <fieldset skraFieldset>\r\n * <legend skraLegend>Choose plan</legend>\r\n * <skra-field>\r\n * <input type=\"radio\" skraRadio name=\"plan\" value=\"free\"\r\n * [checked]=\"plan() === 'free'\" (change)=\"plan.set('free')\" />\r\n * <label skraLabelFor>Free</label>\r\n * </skra-field>\r\n * <skra-field>\r\n * <input type=\"radio\" skraRadio name=\"plan\" value=\"pro\"\r\n * [checked]=\"plan() === 'pro'\" (change)=\"plan.set('pro')\" />\r\n * <label skraLabelFor>Pro</label>\r\n * </skra-field>\r\n * </fieldset>\r\n *\r\n * Toggle state (`checked`) is NOT reflected as `data-skra-*` — slice CSS uses\r\n * the native `:checked` pseudo-class. Browser handles `aria-checked` natively.\r\n */\r\n@Component({\r\n selector: 'input[type=\"radio\"][skraRadio]',\r\n standalone: true,\r\n // Void host — no <ng-content>. See ADR 040 §2.\r\n template: '',\r\n styleUrl: './radio.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n host: {\r\n '[attr.data-skra-disabled]': 'context?.isDisabled() ? \"\" : null',\r\n '[attr.aria-invalid]': 'context?.hasError() ? \"true\" : null',\r\n // Readonly is ARIA-only (native `readonly` doesn't apply to radios per HTML\r\n // spec). No `data-skra-readonly` — dead surface (styling keys off\r\n // `[aria-readonly]`), removed 2026-05-31 to match select's Q-3 ARIA-only\r\n // pattern (Batch 5 cleanroom review).\r\n '[attr.aria-readonly]': 'context?.isReadonly() ? \"true\" : null',\r\n '[attr.aria-describedby]': 'context?.describedByIds() ?? null',\r\n '(click)': 'blockWhenReadonly($event)',\r\n '(keydown)': 'blockKeyWhenReadonly($event)',\r\n },\r\n})\r\nexport class SkraRadio {\r\n private readonly el = inject<ElementRef<HTMLInputElement>>(ElementRef);\r\n readonly context = inject(SkraFieldContext, { optional: true });\r\n\r\n constructor() {\r\n if (this.context) {\r\n const ctx = this.context;\r\n const el = this.el.nativeElement;\r\n effect(() => {\r\n el.id = ctx.fieldId();\r\n });\r\n effect(() => {\r\n el.disabled = ctx.isDisabled();\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Readonly enforcement — native radio has no `[readonly]`, so without this the\r\n * `aria-readonly` we announce would be a lie (the user could still change the\r\n * selection). `preventDefault` on `click` blocks the selection change from both\r\n * pointer and Space/Enter (keyboard activation dispatches a click on form\r\n * controls). Mirrors the `<select skraSelect>` readonly guard.\r\n */\r\n protected blockWhenReadonly(event: Event): void {\r\n if (this.context?.isReadonly()) event.preventDefault();\r\n }\r\n\r\n /**\r\n * Arrow keys move selection between radios in a `name` group WITHOUT a click,\r\n * so they need a separate guard. Block every key except `Tab` (focus traversal)\r\n * when readonly.\r\n */\r\n protected blockKeyWhenReadonly(event: KeyboardEvent): void {\r\n if (this.context?.isReadonly() && event.key !== 'Tab') {\r\n event.preventDefault();\r\n }\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BG;MAsBU,SAAS,CAAA;AACH,IAAA,EAAE,GAAG,MAAM,CAA+B,UAAU,CAAC;IAC7D,OAAO,GAAG,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAE/D,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE;AAChB,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO;AACxB,YAAA,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;YAChC,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE;AACvB,YAAA,CAAC,CAAC;YACF,MAAM,CAAC,MAAK;AACV,gBAAA,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE;AAChC,YAAA,CAAC,CAAC;QACJ;IACF;AAEA;;;;;;AAMG;AACO,IAAA,iBAAiB,CAAC,KAAY,EAAA;AACtC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE;YAAE,KAAK,CAAC,cAAc,EAAE;IACxD;AAEA;;;;AAIG;AACO,IAAA,oBAAoB,CAAC,KAAoB,EAAA;AACjD,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE;YACrD,KAAK,CAAC,cAAc,EAAE;QACxB;IACF;uGArCW,SAAS,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAT,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,SAAS,qdAjBV,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,mrEAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAiBD,SAAS,EAAA,UAAA,EAAA,CAAA;kBArBrB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gCAAgC,EAAA,UAAA,EAC9B,IAAI,EAAA,QAAA,EAEN,EAAE,EAAA,eAAA,EAEK,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;AACJ,wBAAA,2BAA2B,EAAE,mCAAmC;AAChE,wBAAA,qBAAqB,EAAE,qCAAqC;;;;;AAK5D,wBAAA,sBAAsB,EAAE,uCAAuC;AAC/D,wBAAA,yBAAyB,EAAE,mCAAmC;AAC9D,wBAAA,SAAS,EAAE,2BAA2B;AACtC,wBAAA,WAAW,EAAE,8BAA8B;AAC5C,qBAAA,EAAA,MAAA,EAAA,CAAA,mrEAAA,CAAA,EAAA;;;ACxDH;;AAEG;;;;"}
@@ -34,16 +34,20 @@ import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component
34
34
  */
35
35
  class SkraRatingDisplay {
36
36
  /** The rating to display — FLOAT (e.g. 4.3). Consumer-owned (ADR 037); clamped to [0, max]. */
37
- value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
37
+ value = input(0, /* @ts-ignore */
38
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
38
39
  /** Number of stars (semantic count). */
39
- max = input(5, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
40
+ max = input(5, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
40
42
  /** ADR 035 size axis — the only variant axis. */
41
- size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
43
+ size = input('md', /* @ts-ignore */
44
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
42
45
  /** Star slots [1..max] — both layers render the same count. */
43
46
  stars = computed(() => {
44
47
  const n = Math.max(0, Math.floor(this.max()));
45
48
  return Array.from({ length: n }, (_, i) => i + 1);
46
- }, ...(ngDevMode ? [{ debugName: "stars" }] : /* istanbul ignore next */ []));
49
+ }, /* @ts-ignore */
50
+ ...(ngDevMode ? [{ debugName: "stars" }] : /* istanbul ignore next */ []));
47
51
  /**
48
52
  * Fill percentage quantized to 5% buckets (`{0,5,…,100}`) — reflected as `data-skra-fill`,
49
53
  * mapped to the overlay width by the SCSS. 4.3/5 → 86% → bucket 85 (visually exact-enough).
@@ -56,18 +60,19 @@ class SkraRatingDisplay {
56
60
  const ratio = this.value() / max;
57
61
  const clamped = Math.min(1, Math.max(0, ratio));
58
62
  return Math.round(clamped * 20) * 5;
59
- }, ...(ngDevMode ? [{ debugName: "fillBucket" }] : /* istanbul ignore next */ []));
60
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraRatingDisplay, deps: [], target: i0.ɵɵFactoryTarget.Component });
61
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: SkraRatingDisplay, isStandalone: true, selector: "skra-rating-display", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-fill": "fillBucket()", "attr.data-skra-size": "size()" } }, ngImport: i0, template: "<!--\n Two stacked glyph layers: an empty-stars track that defines the box, and a filled-stars\n overlay clipped to the quantized fill width (the SCSS maps data-skra-fill \u2192 inline-size).\n Both layers are decorative (aria-hidden); the accessible name, when any, is on the host\n (consumer-applied role=\"img\" + aria-label \u2014 ADR 033 Path 3).\n-->\n<div class=\"skra-rating-display-track\" aria-hidden=\"true\">\n @for (star of stars(); track star) {\n <svg\n class=\"skra-rating-display-glyph\"\n viewBox=\"0 0 24 24\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\n />\n </svg>\n }\n</div>\n<div class=\"skra-rating-display-fill\" aria-hidden=\"true\">\n @for (star of stars(); track star) {\n <svg\n class=\"skra-rating-display-glyph\"\n viewBox=\"0 0 24 24\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\n />\n </svg>\n }\n</div>\n", styles: [":host{margin:0;position:relative;display:inline-flex;line-height:0;--_star-size: var(--skra-comp-star-rating-star-size-md)}:host([data-skra-size=sm]){--_star-size: var(--skra-comp-star-rating-star-size-sm)}:host([data-skra-size=lg]){--_star-size: var(--skra-comp-star-rating-star-size-lg)}.skra-rating-display-track,.skra-rating-display-fill{display:flex;gap:0;line-height:0}.skra-rating-display-fill{position:absolute;inset-block-start:0;inset-inline-start:0;block-size:100%;inline-size:0;overflow:hidden}.skra-rating-display-track .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-empty)}.skra-rating-display-fill .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-filled)}.skra-rating-display-glyph{flex:0 0 auto;inline-size:var(--_star-size);block-size:var(--_star-size)}:host([data-skra-fill=\"0\"]) .skra-rating-display-fill{inline-size:0%}:host([data-skra-fill=\"5\"]) .skra-rating-display-fill{inline-size:5%}:host([data-skra-fill=\"10\"]) .skra-rating-display-fill{inline-size:10%}:host([data-skra-fill=\"15\"]) .skra-rating-display-fill{inline-size:15%}:host([data-skra-fill=\"20\"]) .skra-rating-display-fill{inline-size:20%}:host([data-skra-fill=\"25\"]) .skra-rating-display-fill{inline-size:25%}:host([data-skra-fill=\"30\"]) .skra-rating-display-fill{inline-size:30%}:host([data-skra-fill=\"35\"]) .skra-rating-display-fill{inline-size:35%}:host([data-skra-fill=\"40\"]) .skra-rating-display-fill{inline-size:40%}:host([data-skra-fill=\"45\"]) .skra-rating-display-fill{inline-size:45%}:host([data-skra-fill=\"50\"]) .skra-rating-display-fill{inline-size:50%}:host([data-skra-fill=\"55\"]) .skra-rating-display-fill{inline-size:55%}:host([data-skra-fill=\"60\"]) .skra-rating-display-fill{inline-size:60%}:host([data-skra-fill=\"65\"]) .skra-rating-display-fill{inline-size:65%}:host([data-skra-fill=\"70\"]) .skra-rating-display-fill{inline-size:70%}:host([data-skra-fill=\"75\"]) .skra-rating-display-fill{inline-size:75%}:host([data-skra-fill=\"80\"]) .skra-rating-display-fill{inline-size:80%}:host([data-skra-fill=\"85\"]) .skra-rating-display-fill{inline-size:85%}:host([data-skra-fill=\"90\"]) .skra-rating-display-fill{inline-size:90%}:host([data-skra-fill=\"95\"]) .skra-rating-display-fill{inline-size:95%}:host([data-skra-fill=\"100\"]) .skra-rating-display-fill{inline-size:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
63
+ }, /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "fillBucket" }] : /* istanbul ignore next */ []));
65
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraRatingDisplay, deps: [], target: i0.ɵɵFactoryTarget.Component });
66
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SkraRatingDisplay, isStandalone: true, selector: "skra-rating-display", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-fill": "fillBucket()", "attr.data-skra-size": "size()" } }, ngImport: i0, template: "<!--\r\n Two stacked glyph layers: an empty-stars track that defines the box, and a filled-stars\r\n overlay clipped to the quantized fill width (the SCSS maps data-skra-fill \u2192 inline-size).\r\n Both layers are decorative (aria-hidden); the accessible name, when any, is on the host\r\n (consumer-applied role=\"img\" + aria-label \u2014 ADR 033 Path 3).\r\n-->\r\n<div class=\"skra-rating-display-track\" aria-hidden=\"true\">\r\n @for (star of stars(); track star) {\r\n <svg\r\n class=\"skra-rating-display-glyph\"\r\n viewBox=\"0 0 24 24\"\r\n focusable=\"false\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\r\n />\r\n </svg>\r\n }\r\n</div>\r\n<div class=\"skra-rating-display-fill\" aria-hidden=\"true\">\r\n @for (star of stars(); track star) {\r\n <svg\r\n class=\"skra-rating-display-glyph\"\r\n viewBox=\"0 0 24 24\"\r\n focusable=\"false\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\r\n />\r\n </svg>\r\n }\r\n</div>\r\n", styles: [":host{margin:0;position:relative;display:inline-flex;line-height:0;--_star-size: var(--skra-comp-star-rating-star-size-md)}:host([data-skra-size=sm]){--_star-size: var(--skra-comp-star-rating-star-size-sm)}:host([data-skra-size=lg]){--_star-size: var(--skra-comp-star-rating-star-size-lg)}.skra-rating-display-track,.skra-rating-display-fill{display:flex;gap:0;line-height:0}.skra-rating-display-fill{position:absolute;inset-block-start:0;inset-inline-start:0;block-size:100%;inline-size:0;overflow:hidden}.skra-rating-display-track .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-empty)}.skra-rating-display-fill .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-filled)}.skra-rating-display-glyph{flex:0 0 auto;inline-size:var(--_star-size);block-size:var(--_star-size)}:host([data-skra-fill=\"0\"]) .skra-rating-display-fill{inline-size:0%}:host([data-skra-fill=\"5\"]) .skra-rating-display-fill{inline-size:5%}:host([data-skra-fill=\"10\"]) .skra-rating-display-fill{inline-size:10%}:host([data-skra-fill=\"15\"]) .skra-rating-display-fill{inline-size:15%}:host([data-skra-fill=\"20\"]) .skra-rating-display-fill{inline-size:20%}:host([data-skra-fill=\"25\"]) .skra-rating-display-fill{inline-size:25%}:host([data-skra-fill=\"30\"]) .skra-rating-display-fill{inline-size:30%}:host([data-skra-fill=\"35\"]) .skra-rating-display-fill{inline-size:35%}:host([data-skra-fill=\"40\"]) .skra-rating-display-fill{inline-size:40%}:host([data-skra-fill=\"45\"]) .skra-rating-display-fill{inline-size:45%}:host([data-skra-fill=\"50\"]) .skra-rating-display-fill{inline-size:50%}:host([data-skra-fill=\"55\"]) .skra-rating-display-fill{inline-size:55%}:host([data-skra-fill=\"60\"]) .skra-rating-display-fill{inline-size:60%}:host([data-skra-fill=\"65\"]) .skra-rating-display-fill{inline-size:65%}:host([data-skra-fill=\"70\"]) .skra-rating-display-fill{inline-size:70%}:host([data-skra-fill=\"75\"]) .skra-rating-display-fill{inline-size:75%}:host([data-skra-fill=\"80\"]) .skra-rating-display-fill{inline-size:80%}:host([data-skra-fill=\"85\"]) .skra-rating-display-fill{inline-size:85%}:host([data-skra-fill=\"90\"]) .skra-rating-display-fill{inline-size:90%}:host([data-skra-fill=\"95\"]) .skra-rating-display-fill{inline-size:95%}:host([data-skra-fill=\"100\"]) .skra-rating-display-fill{inline-size:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
62
67
  }
63
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraRatingDisplay, decorators: [{
68
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraRatingDisplay, decorators: [{
64
69
  type: Component,
65
70
  args: [{ selector: 'skra-rating-display', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
66
71
  // Quantized fractional fill (ADR 021 — data-attr, never [style.--var]).
67
72
  '[attr.data-skra-fill]': 'fillBucket()',
68
73
  // Variant Surface size axis (ADR 035) — reuses the family star-size-* tokens.
69
74
  '[attr.data-skra-size]': 'size()',
70
- }, template: "<!--\n Two stacked glyph layers: an empty-stars track that defines the box, and a filled-stars\n overlay clipped to the quantized fill width (the SCSS maps data-skra-fill \u2192 inline-size).\n Both layers are decorative (aria-hidden); the accessible name, when any, is on the host\n (consumer-applied role=\"img\" + aria-label \u2014 ADR 033 Path 3).\n-->\n<div class=\"skra-rating-display-track\" aria-hidden=\"true\">\n @for (star of stars(); track star) {\n <svg\n class=\"skra-rating-display-glyph\"\n viewBox=\"0 0 24 24\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\n />\n </svg>\n }\n</div>\n<div class=\"skra-rating-display-fill\" aria-hidden=\"true\">\n @for (star of stars(); track star) {\n <svg\n class=\"skra-rating-display-glyph\"\n viewBox=\"0 0 24 24\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\n />\n </svg>\n }\n</div>\n", styles: [":host{margin:0;position:relative;display:inline-flex;line-height:0;--_star-size: var(--skra-comp-star-rating-star-size-md)}:host([data-skra-size=sm]){--_star-size: var(--skra-comp-star-rating-star-size-sm)}:host([data-skra-size=lg]){--_star-size: var(--skra-comp-star-rating-star-size-lg)}.skra-rating-display-track,.skra-rating-display-fill{display:flex;gap:0;line-height:0}.skra-rating-display-fill{position:absolute;inset-block-start:0;inset-inline-start:0;block-size:100%;inline-size:0;overflow:hidden}.skra-rating-display-track .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-empty)}.skra-rating-display-fill .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-filled)}.skra-rating-display-glyph{flex:0 0 auto;inline-size:var(--_star-size);block-size:var(--_star-size)}:host([data-skra-fill=\"0\"]) .skra-rating-display-fill{inline-size:0%}:host([data-skra-fill=\"5\"]) .skra-rating-display-fill{inline-size:5%}:host([data-skra-fill=\"10\"]) .skra-rating-display-fill{inline-size:10%}:host([data-skra-fill=\"15\"]) .skra-rating-display-fill{inline-size:15%}:host([data-skra-fill=\"20\"]) .skra-rating-display-fill{inline-size:20%}:host([data-skra-fill=\"25\"]) .skra-rating-display-fill{inline-size:25%}:host([data-skra-fill=\"30\"]) .skra-rating-display-fill{inline-size:30%}:host([data-skra-fill=\"35\"]) .skra-rating-display-fill{inline-size:35%}:host([data-skra-fill=\"40\"]) .skra-rating-display-fill{inline-size:40%}:host([data-skra-fill=\"45\"]) .skra-rating-display-fill{inline-size:45%}:host([data-skra-fill=\"50\"]) .skra-rating-display-fill{inline-size:50%}:host([data-skra-fill=\"55\"]) .skra-rating-display-fill{inline-size:55%}:host([data-skra-fill=\"60\"]) .skra-rating-display-fill{inline-size:60%}:host([data-skra-fill=\"65\"]) .skra-rating-display-fill{inline-size:65%}:host([data-skra-fill=\"70\"]) .skra-rating-display-fill{inline-size:70%}:host([data-skra-fill=\"75\"]) .skra-rating-display-fill{inline-size:75%}:host([data-skra-fill=\"80\"]) .skra-rating-display-fill{inline-size:80%}:host([data-skra-fill=\"85\"]) .skra-rating-display-fill{inline-size:85%}:host([data-skra-fill=\"90\"]) .skra-rating-display-fill{inline-size:90%}:host([data-skra-fill=\"95\"]) .skra-rating-display-fill{inline-size:95%}:host([data-skra-fill=\"100\"]) .skra-rating-display-fill{inline-size:100%}\n"] }]
75
+ }, template: "<!--\r\n Two stacked glyph layers: an empty-stars track that defines the box, and a filled-stars\r\n overlay clipped to the quantized fill width (the SCSS maps data-skra-fill \u2192 inline-size).\r\n Both layers are decorative (aria-hidden); the accessible name, when any, is on the host\r\n (consumer-applied role=\"img\" + aria-label \u2014 ADR 033 Path 3).\r\n-->\r\n<div class=\"skra-rating-display-track\" aria-hidden=\"true\">\r\n @for (star of stars(); track star) {\r\n <svg\r\n class=\"skra-rating-display-glyph\"\r\n viewBox=\"0 0 24 24\"\r\n focusable=\"false\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\r\n />\r\n </svg>\r\n }\r\n</div>\r\n<div class=\"skra-rating-display-fill\" aria-hidden=\"true\">\r\n @for (star of stars(); track star) {\r\n <svg\r\n class=\"skra-rating-display-glyph\"\r\n viewBox=\"0 0 24 24\"\r\n focusable=\"false\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\r\n />\r\n </svg>\r\n }\r\n</div>\r\n", styles: [":host{margin:0;position:relative;display:inline-flex;line-height:0;--_star-size: var(--skra-comp-star-rating-star-size-md)}:host([data-skra-size=sm]){--_star-size: var(--skra-comp-star-rating-star-size-sm)}:host([data-skra-size=lg]){--_star-size: var(--skra-comp-star-rating-star-size-lg)}.skra-rating-display-track,.skra-rating-display-fill{display:flex;gap:0;line-height:0}.skra-rating-display-fill{position:absolute;inset-block-start:0;inset-inline-start:0;block-size:100%;inline-size:0;overflow:hidden}.skra-rating-display-track .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-empty)}.skra-rating-display-fill .skra-rating-display-glyph{fill:var(--skra-comp-star-rating-star-color-filled)}.skra-rating-display-glyph{flex:0 0 auto;inline-size:var(--_star-size);block-size:var(--_star-size)}:host([data-skra-fill=\"0\"]) .skra-rating-display-fill{inline-size:0%}:host([data-skra-fill=\"5\"]) .skra-rating-display-fill{inline-size:5%}:host([data-skra-fill=\"10\"]) .skra-rating-display-fill{inline-size:10%}:host([data-skra-fill=\"15\"]) .skra-rating-display-fill{inline-size:15%}:host([data-skra-fill=\"20\"]) .skra-rating-display-fill{inline-size:20%}:host([data-skra-fill=\"25\"]) .skra-rating-display-fill{inline-size:25%}:host([data-skra-fill=\"30\"]) .skra-rating-display-fill{inline-size:30%}:host([data-skra-fill=\"35\"]) .skra-rating-display-fill{inline-size:35%}:host([data-skra-fill=\"40\"]) .skra-rating-display-fill{inline-size:40%}:host([data-skra-fill=\"45\"]) .skra-rating-display-fill{inline-size:45%}:host([data-skra-fill=\"50\"]) .skra-rating-display-fill{inline-size:50%}:host([data-skra-fill=\"55\"]) .skra-rating-display-fill{inline-size:55%}:host([data-skra-fill=\"60\"]) .skra-rating-display-fill{inline-size:60%}:host([data-skra-fill=\"65\"]) .skra-rating-display-fill{inline-size:65%}:host([data-skra-fill=\"70\"]) .skra-rating-display-fill{inline-size:70%}:host([data-skra-fill=\"75\"]) .skra-rating-display-fill{inline-size:75%}:host([data-skra-fill=\"80\"]) .skra-rating-display-fill{inline-size:80%}:host([data-skra-fill=\"85\"]) .skra-rating-display-fill{inline-size:85%}:host([data-skra-fill=\"90\"]) .skra-rating-display-fill{inline-size:90%}:host([data-skra-fill=\"95\"]) .skra-rating-display-fill{inline-size:95%}:host([data-skra-fill=\"100\"]) .skra-rating-display-fill{inline-size:100%}\n"] }]
71
76
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
72
77
 
73
78
  // Static fractional rating display — read-only counterpart to skra-star-rating
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-rating-display.mjs","sources":["../../../../libs/ui/rating-display/src/lib/rating-display/rating-display.ts","../../../../libs/ui/rating-display/src/lib/rating-display/rating-display.html","../../../../libs/ui/rating-display/src/index.ts","../../../../libs/ui/rating-display/src/skra-ui-rating-display.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n computed,\n input,\n} from '@angular/core';\n\nexport type RatingDisplaySize = 'sm' | 'md' | 'lg';\n\n/**\n * `<skra-rating-display>` — a static, non-interactive **fractional** rating display\n * (\"4.3 out of 5 stars\"). The read-only counterpart to `skra-star-rating` (the\n * interactive whole-star INPUT in `libs/ui/forms`); a SEPARATE atom by atom-granularity\n * (opposite a11y contract `role=\"img\"` vs `radiogroup`; float vs int; no interaction).\n * Third resident of `libs/ui/data-display`, beside Avatar + StatusDot. See\n * `adr/specs/component-rating-display-spec.md`.\n *\n * **Element, not a decorated native (ADR 012):** there is no native rating element, like\n * StatusDot/Avatar. NOT a Web Component (ADR 000).\n *\n * **Value consumer-owned (ADR 037):** `value` is a plain `input<number>()` (float). The atom\n * owns no value, no `ControlValueAccessor`, no `@angular/forms`.\n *\n * **Fractional fill is CSP-clean (ADR 021):** the fill is QUANTIZED to a `data-skra-fill`\n * bucket reflected on the host (`{0,5,…,100}`); the SCSS maps each bucket to the fill-overlay\n * width. Zero `[style.--var]`, zero inline style — a bounded value is quantizable, so no\n * geometry exception is taken. State Surface all-N/A (non-focusable display; Badge/StatusDot\n * precedent).\n *\n * **Accessible name (ADR 033 Path 3 — the StatusDot/Avatar contract):** the host carries NO\n * default ARIA. The consumer declares semantics:\n * - decorative (Mode A, beside a visible value like \"4.3\") → `aria-hidden=\"true\"`;\n * - meaningful (Mode B, the stars are the sole carrier) → `role=\"img\"` + `aria-label`\n * TOGETHER (e.g. `aria-label=\"Rated 4.3 out of 5\"`).\n * The inner star glyphs are always decorative. NO `ariaLabel` input (boundary #8) — the label\n * is consumer-owned + localizable (the agnostic doctrine; Forge does not hard-code \"out of N\").\n *\n * **Look is skin-owned:** glyph colours + sizes come from the shared family surface\n * (`comp/star-rating.json`). NO `intent`/`emphasis`/`color`/`variant` look-inputs.\n */\n@Component({\n selector: 'skra-rating-display',\n standalone: true,\n templateUrl: './rating-display.html',\n styleUrl: './rating-display.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // Quantized fractional fill (ADR 021 — data-attr, never [style.--var]).\n '[attr.data-skra-fill]': 'fillBucket()',\n // Variant Surface size axis (ADR 035) — reuses the family star-size-* tokens.\n '[attr.data-skra-size]': 'size()',\n },\n})\nexport class SkraRatingDisplay {\n /** The rating to display — FLOAT (e.g. 4.3). Consumer-owned (ADR 037); clamped to [0, max]. */\n readonly value = input<number>(0);\n /** Number of stars (semantic count). */\n readonly max = input<number>(5);\n /** ADR 035 size axis — the only variant axis. */\n readonly size = input<RatingDisplaySize>('md');\n\n /** Star slots [1..max] — both layers render the same count. */\n readonly stars = computed(() => {\n const n = Math.max(0, Math.floor(this.max()));\n return Array.from({ length: n }, (_, i) => i + 1);\n });\n\n /**\n * Fill percentage quantized to 5% buckets (`{0,5,…,100}`) — reflected as `data-skra-fill`,\n * mapped to the overlay width by the SCSS. 4.3/5 → 86% → bucket 85 (visually exact-enough).\n * A bounded, quantizable value is exactly what ADR 021 reflection is for — no style exception.\n */\n readonly fillBucket = computed(() => {\n const max = this.max();\n if (max <= 0) return 0;\n const ratio = this.value() / max;\n const clamped = Math.min(1, Math.max(0, ratio));\n return Math.round(clamped * 20) * 5;\n });\n}\n","<!--\n Two stacked glyph layers: an empty-stars track that defines the box, and a filled-stars\n overlay clipped to the quantized fill width (the SCSS maps data-skra-fill → inline-size).\n Both layers are decorative (aria-hidden); the accessible name, when any, is on the host\n (consumer-applied role=\"img\" + aria-label — ADR 033 Path 3).\n-->\n<div class=\"skra-rating-display-track\" aria-hidden=\"true\">\n @for (star of stars(); track star) {\n <svg\n class=\"skra-rating-display-glyph\"\n viewBox=\"0 0 24 24\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\n />\n </svg>\n }\n</div>\n<div class=\"skra-rating-display-fill\" aria-hidden=\"true\">\n @for (star of stars(); track star) {\n <svg\n class=\"skra-rating-display-glyph\"\n viewBox=\"0 0 24 24\"\n focusable=\"false\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\n />\n </svg>\n }\n</div>\n","// Static fractional rating display — read-only counterpart to skra-star-rating\n// (@skra/ui/star-rating); shares the family glyph surface (comp/star-rating.json).\nexport * from './lib/rating-display/rating-display';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BG;MAeU,iBAAiB,CAAA;;AAEnB,IAAA,KAAK,GAAG,KAAK,CAAS,CAAC,4EAAC;;AAExB,IAAA,GAAG,GAAG,KAAK,CAAS,CAAC,0EAAC;;AAEtB,IAAA,IAAI,GAAG,KAAK,CAAoB,IAAI,2EAAC;;AAGrC,IAAA,KAAK,GAAG,QAAQ,CAAC,MAAK;AAC7B,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACnD,IAAA,CAAC,4EAAC;AAEF;;;;AAIG;AACM,IAAA,UAAU,GAAG,QAAQ,CAAC,MAAK;AAClC,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,GAAG,IAAI,CAAC;AAAE,YAAA,OAAO,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,GAAG;AAChC,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC;AACrC,IAAA,CAAC,iFAAC;uGAzBS,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAjB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,iBAAiB,4hBCvD9B,mmCAkCA,EAAA,MAAA,EAAA,CAAA,+yEAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FDqBa,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAd7B,SAAS;+BACE,qBAAqB,EAAA,UAAA,EACnB,IAAI,EAAA,eAAA,EAGC,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,uBAAuB,EAAE,cAAc;;AAEvC,wBAAA,uBAAuB,EAAE,QAAQ;AAClC,qBAAA,EAAA,QAAA,EAAA,mmCAAA,EAAA,MAAA,EAAA,CAAA,+yEAAA,CAAA,EAAA;;;AErDH;AACA;;ACDA;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-rating-display.mjs","sources":["../../../../libs/ui/rating-display/src/lib/rating-display/rating-display.ts","../../../../libs/ui/rating-display/src/lib/rating-display/rating-display.html","../../../../libs/ui/rating-display/src/index.ts","../../../../libs/ui/rating-display/src/skra-ui-rating-display.ts"],"sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ViewEncapsulation,\r\n computed,\r\n input,\r\n} from '@angular/core';\r\n\r\nexport type RatingDisplaySize = 'sm' | 'md' | 'lg';\r\n\r\n/**\r\n * `<skra-rating-display>` — a static, non-interactive **fractional** rating display\r\n * (\"4.3 out of 5 stars\"). The read-only counterpart to `skra-star-rating` (the\r\n * interactive whole-star INPUT in `libs/ui/forms`); a SEPARATE atom by atom-granularity\r\n * (opposite a11y contract `role=\"img\"` vs `radiogroup`; float vs int; no interaction).\r\n * Third resident of `libs/ui/data-display`, beside Avatar + StatusDot. See\r\n * `adr/specs/component-rating-display-spec.md`.\r\n *\r\n * **Element, not a decorated native (ADR 012):** there is no native rating element, like\r\n * StatusDot/Avatar. NOT a Web Component (ADR 000).\r\n *\r\n * **Value consumer-owned (ADR 037):** `value` is a plain `input<number>()` (float). The atom\r\n * owns no value, no `ControlValueAccessor`, no `@angular/forms`.\r\n *\r\n * **Fractional fill is CSP-clean (ADR 021):** the fill is QUANTIZED to a `data-skra-fill`\r\n * bucket reflected on the host (`{0,5,…,100}`); the SCSS maps each bucket to the fill-overlay\r\n * width. Zero `[style.--var]`, zero inline style — a bounded value is quantizable, so no\r\n * geometry exception is taken. State Surface all-N/A (non-focusable display; Badge/StatusDot\r\n * precedent).\r\n *\r\n * **Accessible name (ADR 033 Path 3 — the StatusDot/Avatar contract):** the host carries NO\r\n * default ARIA. The consumer declares semantics:\r\n * - decorative (Mode A, beside a visible value like \"4.3\") → `aria-hidden=\"true\"`;\r\n * - meaningful (Mode B, the stars are the sole carrier) → `role=\"img\"` + `aria-label`\r\n * TOGETHER (e.g. `aria-label=\"Rated 4.3 out of 5\"`).\r\n * The inner star glyphs are always decorative. NO `ariaLabel` input (boundary #8) — the label\r\n * is consumer-owned + localizable (the agnostic doctrine; Forge does not hard-code \"out of N\").\r\n *\r\n * **Look is skin-owned:** glyph colours + sizes come from the shared family surface\r\n * (`comp/star-rating.json`). NO `intent`/`emphasis`/`color`/`variant` look-inputs.\r\n */\r\n@Component({\r\n selector: 'skra-rating-display',\r\n standalone: true,\r\n templateUrl: './rating-display.html',\r\n styleUrl: './rating-display.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n host: {\r\n // Quantized fractional fill (ADR 021 — data-attr, never [style.--var]).\r\n '[attr.data-skra-fill]': 'fillBucket()',\r\n // Variant Surface size axis (ADR 035) — reuses the family star-size-* tokens.\r\n '[attr.data-skra-size]': 'size()',\r\n },\r\n})\r\nexport class SkraRatingDisplay {\r\n /** The rating to display — FLOAT (e.g. 4.3). Consumer-owned (ADR 037); clamped to [0, max]. */\r\n readonly value = input<number>(0);\r\n /** Number of stars (semantic count). */\r\n readonly max = input<number>(5);\r\n /** ADR 035 size axis — the only variant axis. */\r\n readonly size = input<RatingDisplaySize>('md');\r\n\r\n /** Star slots [1..max] — both layers render the same count. */\r\n readonly stars = computed(() => {\r\n const n = Math.max(0, Math.floor(this.max()));\r\n return Array.from({ length: n }, (_, i) => i + 1);\r\n });\r\n\r\n /**\r\n * Fill percentage quantized to 5% buckets (`{0,5,…,100}`) — reflected as `data-skra-fill`,\r\n * mapped to the overlay width by the SCSS. 4.3/5 → 86% → bucket 85 (visually exact-enough).\r\n * A bounded, quantizable value is exactly what ADR 021 reflection is for — no style exception.\r\n */\r\n readonly fillBucket = computed(() => {\r\n const max = this.max();\r\n if (max <= 0) return 0;\r\n const ratio = this.value() / max;\r\n const clamped = Math.min(1, Math.max(0, ratio));\r\n return Math.round(clamped * 20) * 5;\r\n });\r\n}\r\n","<!--\r\n Two stacked glyph layers: an empty-stars track that defines the box, and a filled-stars\r\n overlay clipped to the quantized fill width (the SCSS maps data-skra-fill → inline-size).\r\n Both layers are decorative (aria-hidden); the accessible name, when any, is on the host\r\n (consumer-applied role=\"img\" + aria-label — ADR 033 Path 3).\r\n-->\r\n<div class=\"skra-rating-display-track\" aria-hidden=\"true\">\r\n @for (star of stars(); track star) {\r\n <svg\r\n class=\"skra-rating-display-glyph\"\r\n viewBox=\"0 0 24 24\"\r\n focusable=\"false\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\r\n />\r\n </svg>\r\n }\r\n</div>\r\n<div class=\"skra-rating-display-fill\" aria-hidden=\"true\">\r\n @for (star of stars(); track star) {\r\n <svg\r\n class=\"skra-rating-display-glyph\"\r\n viewBox=\"0 0 24 24\"\r\n focusable=\"false\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z\"\r\n />\r\n </svg>\r\n }\r\n</div>\r\n","// Static fractional rating display — read-only counterpart to skra-star-rating\r\n// (@skra/ui/star-rating); shares the family glyph surface (comp/star-rating.json).\r\nexport * from './lib/rating-display/rating-display';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BG;MAeU,iBAAiB,CAAA;;IAEnB,KAAK,GAAG,KAAK,CAAS,CAAC;8EAAC;;IAExB,GAAG,GAAG,KAAK,CAAS,CAAC;4EAAC;;IAEtB,IAAI,GAAG,KAAK,CAAoB,IAAI;6EAAC;;AAGrC,IAAA,KAAK,GAAG,QAAQ,CAAC,MAAK;AAC7B,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC;8EAAC;AAEF;;;;AAIG;AACM,IAAA,UAAU,GAAG,QAAQ,CAAC,MAAK;AAClC,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtB,IAAI,GAAG,IAAI,CAAC;AAAE,YAAA,OAAO,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,GAAG;AAChC,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC;IACrC,CAAC;mFAAC;uGAzBS,iBAAiB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAjB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,iBAAiB,4hBCvD9B,uqCAkCA,EAAA,MAAA,EAAA,CAAA,+yEAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FDqBa,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAd7B,SAAS;+BACE,qBAAqB,EAAA,UAAA,EACnB,IAAI,EAAA,eAAA,EAGC,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,uBAAuB,EAAE,cAAc;;AAEvC,wBAAA,uBAAuB,EAAE,QAAQ;AAClC,qBAAA,EAAA,QAAA,EAAA,uqCAAA,EAAA,MAAA,EAAA,CAAA,+yEAAA,CAAA,EAAA;;;AErDH;AACA;;ACDA;;AAEG;;;;"}
@@ -22,17 +22,20 @@ export { SkraOption, SkraOptionDetail, SkraOptionLeading, SkraSelectTrigger } fr
22
22
  */
23
23
  class SkraSelect extends _SkraSelectBase {
24
24
  /** Consumer-owned value channel (ADR 037). */
25
- value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
25
+ value = input(/* @ts-ignore */
26
+ ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
26
27
  /** Fires ONLY on user selection — never on a programmatic `value()` change (ADR 037). */
27
28
  valueChange = output();
28
29
  /** Cardinality hint (ADR 0046) — single-select. */
29
30
  multiple = false;
30
31
  /** The option whose value matches `value()` (ADR 037 selection model) — feeds the trigger. */
31
- selectedOption = computed(() => this.options().find((o) => this.isSelected(o.value())) ?? null, ...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
32
+ selectedOption = computed(() => this.options().find((o) => this.isSelected(o.value())) ?? null, /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
32
34
  triggerContext = computed(() => ({
33
35
  $implicit: this.value(),
34
36
  option: this.selectedOption(),
35
- }), ...(ngDevMode ? [{ debugName: "triggerContext" }] : /* istanbul ignore next */ []));
37
+ }), /* @ts-ignore */
38
+ ...(ngDevMode ? [{ debugName: "triggerContext" }] : /* istanbul ignore next */ []));
36
39
  // ── SkraListboxPanel seam ──
37
40
  isSelected(value) {
38
41
  const current = this.value();
@@ -45,101 +48,101 @@ class SkraSelect extends _SkraSelectBase {
45
48
  activeSeedItem() {
46
49
  return this.selectedOption();
47
50
  }
48
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraSelect, deps: null, target: i0.ɵɵFactoryTarget.Component });
49
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: SkraSelect, isStandalone: true, selector: "skra-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, providers: [{ provide: SKRA_LISTBOX_PANEL, useExisting: SkraSelect }], exportAs: ["skraSelect"], usesInheritance: true, ngImport: i0, template: `
50
- <button
51
- #trigger
52
- class="skra-select-trigger"
53
- type="button"
54
- role="combobox"
55
- aria-haspopup="listbox"
56
- [attr.id]="triggerId()"
57
- [attr.popovertarget]="panelId"
58
- [attr.aria-controls]="panelId"
59
- [attr.aria-expanded]="isOpen()"
60
- [attr.aria-activedescendant]="activeOptionId()"
61
- [attr.aria-describedby]="describedBy()"
62
- [attr.aria-invalid]="hasError() ? 'true' : null"
63
- [attr.aria-required]="isRequired() ? 'true' : null"
64
- [attr.aria-readonly]="isReadonly() ? 'true' : null"
65
- [disabled]="isDisabled()"
66
- (click)="onTriggerClick($event)"
67
- (keydown)="onTriggerKeydown($event)"
68
- >
69
- <span class="skra-select-trigger-value">
70
- @if (triggerTemplate(); as tpl) {
71
- <ng-container
72
- [ngTemplateOutlet]="tpl"
73
- [ngTemplateOutletContext]="triggerContext()"
74
- ></ng-container>
75
- } @else if (selectedOption(); as opt) {
76
- {{ opt.getLabel() }}
77
- } @else {
78
- <span class="skra-select-placeholder">{{ placeholder() }}</span>
79
- }
80
- </span>
81
- <span class="skra-select-arrow" aria-hidden="true"></span>
82
- </button>
83
-
84
- <div
85
- #panel
86
- class="skra-select-listbox"
87
- [id]="panelId"
88
- role="listbox"
89
- popover="auto"
90
- (toggle)="onPanelToggle($event)"
91
- >
92
- <ng-content></ng-content>
93
- </div>
51
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSelect, deps: null, target: i0.ɵɵFactoryTarget.Component });
52
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SkraSelect, isStandalone: true, selector: "skra-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, providers: [{ provide: SKRA_LISTBOX_PANEL, useExisting: SkraSelect }], exportAs: ["skraSelect"], usesInheritance: true, ngImport: i0, template: `
53
+ <button
54
+ #trigger
55
+ class="skra-select-trigger"
56
+ type="button"
57
+ role="combobox"
58
+ aria-haspopup="listbox"
59
+ [attr.id]="triggerId()"
60
+ [attr.popovertarget]="panelId"
61
+ [attr.aria-controls]="panelId"
62
+ [attr.aria-expanded]="isOpen()"
63
+ [attr.aria-activedescendant]="activeOptionId()"
64
+ [attr.aria-describedby]="describedBy()"
65
+ [attr.aria-invalid]="hasError() ? 'true' : null"
66
+ [attr.aria-required]="isRequired() ? 'true' : null"
67
+ [attr.aria-readonly]="isReadonly() ? 'true' : null"
68
+ [disabled]="isDisabled()"
69
+ (click)="onTriggerClick($event)"
70
+ (keydown)="onTriggerKeydown($event)"
71
+ >
72
+ <span class="skra-select-trigger-value">
73
+ @if (triggerTemplate(); as tpl) {
74
+ <ng-container
75
+ [ngTemplateOutlet]="tpl"
76
+ [ngTemplateOutletContext]="triggerContext()"
77
+ ></ng-container>
78
+ } @else if (selectedOption(); as opt) {
79
+ {{ opt.getLabel() }}
80
+ } @else {
81
+ <span class="skra-select-placeholder">{{ placeholder() }}</span>
82
+ }
83
+ </span>
84
+ <span class="skra-select-arrow" aria-hidden="true"></span>
85
+ </button>
86
+
87
+ <div
88
+ #panel
89
+ class="skra-select-listbox"
90
+ [id]="panelId"
91
+ role="listbox"
92
+ popover="auto"
93
+ (toggle)="onPanelToggle($event)"
94
+ >
95
+ <ng-content></ng-content>
96
+ </div>
94
97
  `, isInline: true, styles: ["@charset \"UTF-8\";:host{display:block;box-sizing:border-box;margin:0}.skra-select-trigger{display:flex;align-items:center;gap:var(--skra-comp-select-gap);inline-size:100%;min-block-size:var(--skra-comp-select-min-block-size);box-sizing:border-box;margin:0;padding-block:var(--skra-comp-select-padding-block);padding-inline:var(--skra-comp-select-padding-inline);text-align:start;cursor:pointer;background:var(--skra-comp-select-background);color:var(--skra-comp-select-foreground);border-color:var(--skra-comp-select-border-color);border-width:var(--skra-comp-select-border-width);border-style:var(--skra-comp-select-border-style);border-radius:var(--skra-comp-select-radius);box-shadow:var(--skra-comp-select-shadow);font-family:var(--skra-sys-typography-role-body-medium-font-family);font-size:var(--skra-sys-typography-role-body-medium-font-size);font-weight:var(--skra-sys-typography-role-body-medium-font-weight);line-height:var(--skra-sys-typography-role-body-medium-line-height);letter-spacing:var(--skra-sys-typography-role-body-medium-letter-spacing);transition:background-color var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing),border-color var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing)}.skra-select-trigger:hover:not(:disabled):not([aria-readonly=true]){background:var(--skra-comp-select-background-hover)}.skra-select-trigger:focus-visible{outline:var(--skra-comp-select-focus-ring-width) solid var(--skra-comp-select-focus-ring-color);outline-offset:.125rem}.skra-select-trigger:disabled{background:var(--skra-comp-select-background-disabled);color:var(--skra-comp-select-foreground-disabled);cursor:not-allowed}.skra-select-trigger[aria-readonly=true]{background:var(--skra-comp-select-background-readonly);cursor:default}.skra-select-trigger[aria-invalid=true]{border-color:var(--skra-comp-select-border-color-validation-invalid)}.skra-select-trigger-value{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--skra-comp-icon-size: var(--skra-comp-select-icon-size)}.skra-select-placeholder{color:var(--skra-comp-select-foreground-disabled)}.skra-select-arrow{flex:none;inline-size:var(--skra-comp-select-arrow-icon-size);block-size:var(--skra-comp-select-arrow-icon-size);background-color:var(--skra-comp-select-arrow-color);mask:url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%209l6%206%206-6'%20fill='none'%20stroke='%23000'%20stroke-width='2'/%3E%3C/svg%3E\") no-repeat center/contain;-webkit-mask:url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%209l6%206%206-6'%20fill='none'%20stroke='%23000'%20stroke-width='2'/%3E%3C/svg%3E\") no-repeat center/contain;transition:transform var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing)}.skra-select-trigger[aria-expanded=true] .skra-select-arrow{transform:rotate(180deg)}.skra-select-listbox{position:fixed;margin:0;inset:auto;box-sizing:border-box;padding-block:var(--skra-comp-select-panel-padding-block);padding-inline:var(--skra-comp-select-panel-padding-inline);background:var(--skra-comp-select-panel-background);color:var(--skra-comp-select-panel-foreground);border:var(--skra-comp-select-border-width) solid var(--skra-comp-select-panel-border-color);border-radius:var(--skra-comp-select-panel-radius);box-shadow:var(--skra-comp-select-panel-shadow);inline-size:max-content;max-block-size:var(--skra-comp-select-panel-max-block-size);overflow-y:auto;flex-direction:column;opacity:0;transition:opacity var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing),display var(--skra-comp-select-motion-duration) allow-discrete,overlay var(--skra-comp-select-motion-duration) allow-discrete}.skra-select-listbox:popover-open{display:flex;opacity:1}@starting-style{.skra-select-listbox:popover-open{opacity:0}}@media(prefers-reduced-motion:reduce){.skra-select-trigger,.skra-select-arrow,.skra-select-listbox{transition-duration:0ms}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
95
98
  }
96
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraSelect, decorators: [{
99
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSelect, decorators: [{
97
100
  type: Component,
98
- args: [{ selector: 'skra-select', standalone: true, exportAs: 'skraSelect', imports: [NgTemplateOutlet], template: `
99
- <button
100
- #trigger
101
- class="skra-select-trigger"
102
- type="button"
103
- role="combobox"
104
- aria-haspopup="listbox"
105
- [attr.id]="triggerId()"
106
- [attr.popovertarget]="panelId"
107
- [attr.aria-controls]="panelId"
108
- [attr.aria-expanded]="isOpen()"
109
- [attr.aria-activedescendant]="activeOptionId()"
110
- [attr.aria-describedby]="describedBy()"
111
- [attr.aria-invalid]="hasError() ? 'true' : null"
112
- [attr.aria-required]="isRequired() ? 'true' : null"
113
- [attr.aria-readonly]="isReadonly() ? 'true' : null"
114
- [disabled]="isDisabled()"
115
- (click)="onTriggerClick($event)"
116
- (keydown)="onTriggerKeydown($event)"
117
- >
118
- <span class="skra-select-trigger-value">
119
- @if (triggerTemplate(); as tpl) {
120
- <ng-container
121
- [ngTemplateOutlet]="tpl"
122
- [ngTemplateOutletContext]="triggerContext()"
123
- ></ng-container>
124
- } @else if (selectedOption(); as opt) {
125
- {{ opt.getLabel() }}
126
- } @else {
127
- <span class="skra-select-placeholder">{{ placeholder() }}</span>
128
- }
129
- </span>
130
- <span class="skra-select-arrow" aria-hidden="true"></span>
131
- </button>
132
-
133
- <div
134
- #panel
135
- class="skra-select-listbox"
136
- [id]="panelId"
137
- role="listbox"
138
- popover="auto"
139
- (toggle)="onPanelToggle($event)"
140
- >
141
- <ng-content></ng-content>
142
- </div>
101
+ args: [{ selector: 'skra-select', standalone: true, exportAs: 'skraSelect', imports: [NgTemplateOutlet], template: `
102
+ <button
103
+ #trigger
104
+ class="skra-select-trigger"
105
+ type="button"
106
+ role="combobox"
107
+ aria-haspopup="listbox"
108
+ [attr.id]="triggerId()"
109
+ [attr.popovertarget]="panelId"
110
+ [attr.aria-controls]="panelId"
111
+ [attr.aria-expanded]="isOpen()"
112
+ [attr.aria-activedescendant]="activeOptionId()"
113
+ [attr.aria-describedby]="describedBy()"
114
+ [attr.aria-invalid]="hasError() ? 'true' : null"
115
+ [attr.aria-required]="isRequired() ? 'true' : null"
116
+ [attr.aria-readonly]="isReadonly() ? 'true' : null"
117
+ [disabled]="isDisabled()"
118
+ (click)="onTriggerClick($event)"
119
+ (keydown)="onTriggerKeydown($event)"
120
+ >
121
+ <span class="skra-select-trigger-value">
122
+ @if (triggerTemplate(); as tpl) {
123
+ <ng-container
124
+ [ngTemplateOutlet]="tpl"
125
+ [ngTemplateOutletContext]="triggerContext()"
126
+ ></ng-container>
127
+ } @else if (selectedOption(); as opt) {
128
+ {{ opt.getLabel() }}
129
+ } @else {
130
+ <span class="skra-select-placeholder">{{ placeholder() }}</span>
131
+ }
132
+ </span>
133
+ <span class="skra-select-arrow" aria-hidden="true"></span>
134
+ </button>
135
+
136
+ <div
137
+ #panel
138
+ class="skra-select-listbox"
139
+ [id]="panelId"
140
+ role="listbox"
141
+ popover="auto"
142
+ (toggle)="onPanelToggle($event)"
143
+ >
144
+ <ng-content></ng-content>
145
+ </div>
143
146
  `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, providers: [{ provide: SKRA_LISTBOX_PANEL, useExisting: SkraSelect }], styles: ["@charset \"UTF-8\";:host{display:block;box-sizing:border-box;margin:0}.skra-select-trigger{display:flex;align-items:center;gap:var(--skra-comp-select-gap);inline-size:100%;min-block-size:var(--skra-comp-select-min-block-size);box-sizing:border-box;margin:0;padding-block:var(--skra-comp-select-padding-block);padding-inline:var(--skra-comp-select-padding-inline);text-align:start;cursor:pointer;background:var(--skra-comp-select-background);color:var(--skra-comp-select-foreground);border-color:var(--skra-comp-select-border-color);border-width:var(--skra-comp-select-border-width);border-style:var(--skra-comp-select-border-style);border-radius:var(--skra-comp-select-radius);box-shadow:var(--skra-comp-select-shadow);font-family:var(--skra-sys-typography-role-body-medium-font-family);font-size:var(--skra-sys-typography-role-body-medium-font-size);font-weight:var(--skra-sys-typography-role-body-medium-font-weight);line-height:var(--skra-sys-typography-role-body-medium-line-height);letter-spacing:var(--skra-sys-typography-role-body-medium-letter-spacing);transition:background-color var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing),border-color var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing)}.skra-select-trigger:hover:not(:disabled):not([aria-readonly=true]){background:var(--skra-comp-select-background-hover)}.skra-select-trigger:focus-visible{outline:var(--skra-comp-select-focus-ring-width) solid var(--skra-comp-select-focus-ring-color);outline-offset:.125rem}.skra-select-trigger:disabled{background:var(--skra-comp-select-background-disabled);color:var(--skra-comp-select-foreground-disabled);cursor:not-allowed}.skra-select-trigger[aria-readonly=true]{background:var(--skra-comp-select-background-readonly);cursor:default}.skra-select-trigger[aria-invalid=true]{border-color:var(--skra-comp-select-border-color-validation-invalid)}.skra-select-trigger-value{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--skra-comp-icon-size: var(--skra-comp-select-icon-size)}.skra-select-placeholder{color:var(--skra-comp-select-foreground-disabled)}.skra-select-arrow{flex:none;inline-size:var(--skra-comp-select-arrow-icon-size);block-size:var(--skra-comp-select-arrow-icon-size);background-color:var(--skra-comp-select-arrow-color);mask:url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%209l6%206%206-6'%20fill='none'%20stroke='%23000'%20stroke-width='2'/%3E%3C/svg%3E\") no-repeat center/contain;-webkit-mask:url(\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%209l6%206%206-6'%20fill='none'%20stroke='%23000'%20stroke-width='2'/%3E%3C/svg%3E\") no-repeat center/contain;transition:transform var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing)}.skra-select-trigger[aria-expanded=true] .skra-select-arrow{transform:rotate(180deg)}.skra-select-listbox{position:fixed;margin:0;inset:auto;box-sizing:border-box;padding-block:var(--skra-comp-select-panel-padding-block);padding-inline:var(--skra-comp-select-panel-padding-inline);background:var(--skra-comp-select-panel-background);color:var(--skra-comp-select-panel-foreground);border:var(--skra-comp-select-border-width) solid var(--skra-comp-select-panel-border-color);border-radius:var(--skra-comp-select-panel-radius);box-shadow:var(--skra-comp-select-panel-shadow);inline-size:max-content;max-block-size:var(--skra-comp-select-panel-max-block-size);overflow-y:auto;flex-direction:column;opacity:0;transition:opacity var(--skra-comp-select-motion-duration) var(--skra-comp-select-motion-easing),display var(--skra-comp-select-motion-duration) allow-discrete,overlay var(--skra-comp-select-motion-duration) allow-discrete}.skra-select-listbox:popover-open{display:flex;opacity:1}@starting-style{.skra-select-listbox:popover-open{opacity:0}}@media(prefers-reduced-motion:reduce){.skra-select-trigger,.skra-select-arrow,.skra-select-listbox{transition-duration:0ms}}\n"] }]
144
147
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
145
148
 
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-select.mjs","sources":["../../../../libs/ui/select/src/lib/select/select.ts","../../../../libs/ui/select/src/skra-ui-select.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n computed,\n input,\n output,\n} from '@angular/core';\n\nimport { _SkraSelectBase } from '@skra/ui/core';\nimport {\n SKRA_LISTBOX_PANEL,\n type SkraListboxKeyboardItem,\n} from '@skra/ui/core';\n\n/**\n * SkraSelect — a custom single-select listbox: the WAI-ARIA APG **Select-Only Combobox** pattern. A\n * value-display **trigger** (`role=\"combobox\"`) opens a top-layer **listbox** panel (Popover API,\n * `popover=\"auto\"`, ADR 0044) of projected `skra-option` children. The thin single-select concrete\n * over `_SkraSelectBase` (ADR 0046 — cardinality split): it adds the honest `value: T` channel + the\n * replace-and-close selection model; everything else (overlay/keyboard/field substrate) lives in the\n * shared base, shared verbatim with `skra-multi-select`.\n *\n * - **Value (ADR 037):** `value` is a consumer-owned `input<T>()` + `valueChange` output — NO\n * `@angular/forms`, no CVA. Selection is signal-derived; equality is a strategy seam (`Object.is`\n * default + opt-in `compareWith`).\n * - **Trigger content (ADR 0045):** default = the selected option's `getLabel()` text (emoji/glyph\n * leading free); rich non-text triggers opt in via `<ng-template skraSelectTrigger>`.\n *\n * Full contract: adr/specs/component-select-spec.md.\n */\n@Component({\n selector: 'skra-select',\n standalone: true,\n exportAs: 'skraSelect',\n imports: [NgTemplateOutlet],\n template: `\n <button\n #trigger\n class=\"skra-select-trigger\"\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.id]=\"triggerId()\"\n [attr.popovertarget]=\"panelId\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"hasError() ? 'true' : null\"\n [attr.aria-required]=\"isRequired() ? 'true' : null\"\n [attr.aria-readonly]=\"isReadonly() ? 'true' : null\"\n [disabled]=\"isDisabled()\"\n (click)=\"onTriggerClick($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"skra-select-trigger-value\">\n @if (triggerTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"triggerContext()\"\n ></ng-container>\n } @else if (selectedOption(); as opt) {\n {{ opt.getLabel() }}\n } @else {\n <span class=\"skra-select-placeholder\">{{ placeholder() }}</span>\n }\n </span>\n <span class=\"skra-select-arrow\" aria-hidden=\"true\"></span>\n </button>\n\n <div\n #panel\n class=\"skra-select-listbox\"\n [id]=\"panelId\"\n role=\"listbox\"\n popover=\"auto\"\n (toggle)=\"onPanelToggle($event)\"\n >\n <ng-content></ng-content>\n </div>\n `,\n styleUrl: './select.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n providers: [{ provide: SKRA_LISTBOX_PANEL, useExisting: SkraSelect }],\n})\nexport class SkraSelect<T = unknown> extends _SkraSelectBase<T> {\n /** Consumer-owned value channel (ADR 037). */\n readonly value = input<T>();\n /** Fires ONLY on user selection — never on a programmatic `value()` change (ADR 037). */\n readonly valueChange = output<T>();\n /** Cardinality hint (ADR 0046) — single-select. */\n readonly multiple = false;\n\n /** The option whose value matches `value()` (ADR 037 selection model) — feeds the trigger. */\n protected readonly selectedOption = computed(\n () => this.options().find((o) => this.isSelected(o.value())) ?? null,\n );\n\n protected readonly triggerContext = computed(() => ({\n $implicit: this.value(),\n option: this.selectedOption(),\n }));\n\n // ── SkraListboxPanel seam ──\n isSelected(value: unknown): boolean {\n const current = this.value();\n return current !== undefined && this.compareWith()(current, value as T);\n }\n\n selectValue(value: unknown): void {\n this.valueChange.emit(value as T);\n this.close(); // single-select: pick + close\n }\n\n protected activeSeedItem(): SkraListboxKeyboardItem | null {\n return this.selectedOption();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAgBA;;;;;;;;;;;;;;;AAeG;AAyDG,MAAO,UAAwB,SAAQ,eAAkB,CAAA;;IAEpD,KAAK,GAAG,KAAK,CAAA,IAAA,SAAA,GAAA,CAAA,SAAA,EAAA,EAAA,SAAA,EAAA,OAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAK;;IAElB,WAAW,GAAG,MAAM,EAAK;;IAEzB,QAAQ,GAAG,KAAK;;AAGN,IAAA,cAAc,GAAG,QAAQ,CAC1C,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,IAAI,qFACrE;AAEkB,IAAA,cAAc,GAAG,QAAQ,CAAC,OAAO;AAClD,QAAA,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE;AACvB,QAAA,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE;AAC9B,KAAA,CAAC,qFAAC;;AAGH,IAAA,UAAU,CAAC,KAAc,EAAA;AACvB,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE;AAC5B,QAAA,OAAO,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,KAAU,CAAC;IACzE;AAEA,IAAA,WAAW,CAAC,KAAc,EAAA;AACxB,QAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAU,CAAC;AACjC,QAAA,IAAI,CAAC,KAAK,EAAE,CAAC;IACf;IAEU,cAAc,GAAA;AACtB,QAAA,OAAO,IAAI,CAAC,cAAc,EAAE;IAC9B;uGA/BW,UAAU,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAV,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAU,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,WAAA,EAAA,aAAA,EAAA,EAAA,SAAA,EAFV,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAA,QAAA,EAAA,CAAA,YAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAjD3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,g/HAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EA9CS,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAoDf,UAAU,EAAA,UAAA,EAAA,CAAA;kBAxDtB,SAAS;+BACE,aAAa,EAAA,UAAA,EACX,IAAI,EAAA,QAAA,EACN,YAAY,WACb,CAAC,gBAAgB,CAAC,EAAA,QAAA,EACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CT,EAAA,CAAA,EAAA,eAAA,EAEgB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,SAAA,EAC9B,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAA,UAAY,EAAE,CAAC,EAAA,MAAA,EAAA,CAAA,g/HAAA,CAAA,EAAA;;;ACtFvE;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-select.mjs","sources":["../../../../libs/ui/select/src/lib/select/select.ts","../../../../libs/ui/select/src/skra-ui-select.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\r\nimport {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ViewEncapsulation,\r\n computed,\r\n input,\r\n output,\r\n} from '@angular/core';\r\n\r\nimport { _SkraSelectBase } from '@skra/ui/core';\r\nimport {\r\n SKRA_LISTBOX_PANEL,\r\n type SkraListboxKeyboardItem,\r\n} from '@skra/ui/core';\r\n\r\n/**\r\n * SkraSelect — a custom single-select listbox: the WAI-ARIA APG **Select-Only Combobox** pattern. A\r\n * value-display **trigger** (`role=\"combobox\"`) opens a top-layer **listbox** panel (Popover API,\r\n * `popover=\"auto\"`, ADR 0044) of projected `skra-option` children. The thin single-select concrete\r\n * over `_SkraSelectBase` (ADR 0046 — cardinality split): it adds the honest `value: T` channel + the\r\n * replace-and-close selection model; everything else (overlay/keyboard/field substrate) lives in the\r\n * shared base, shared verbatim with `skra-multi-select`.\r\n *\r\n * - **Value (ADR 037):** `value` is a consumer-owned `input<T>()` + `valueChange` output — NO\r\n * `@angular/forms`, no CVA. Selection is signal-derived; equality is a strategy seam (`Object.is`\r\n * default + opt-in `compareWith`).\r\n * - **Trigger content (ADR 0045):** default = the selected option's `getLabel()` text (emoji/glyph\r\n * leading free); rich non-text triggers opt in via `<ng-template skraSelectTrigger>`.\r\n *\r\n * Full contract: adr/specs/component-select-spec.md.\r\n */\r\n@Component({\r\n selector: 'skra-select',\r\n standalone: true,\r\n exportAs: 'skraSelect',\r\n imports: [NgTemplateOutlet],\r\n template: `\r\n <button\r\n #trigger\r\n class=\"skra-select-trigger\"\r\n type=\"button\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.id]=\"triggerId()\"\r\n [attr.popovertarget]=\"panelId\"\r\n [attr.aria-controls]=\"panelId\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n [attr.aria-activedescendant]=\"activeOptionId()\"\r\n [attr.aria-describedby]=\"describedBy()\"\r\n [attr.aria-invalid]=\"hasError() ? 'true' : null\"\r\n [attr.aria-required]=\"isRequired() ? 'true' : null\"\r\n [attr.aria-readonly]=\"isReadonly() ? 'true' : null\"\r\n [disabled]=\"isDisabled()\"\r\n (click)=\"onTriggerClick($event)\"\r\n (keydown)=\"onTriggerKeydown($event)\"\r\n >\r\n <span class=\"skra-select-trigger-value\">\r\n @if (triggerTemplate(); as tpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"tpl\"\r\n [ngTemplateOutletContext]=\"triggerContext()\"\r\n ></ng-container>\r\n } @else if (selectedOption(); as opt) {\r\n {{ opt.getLabel() }}\r\n } @else {\r\n <span class=\"skra-select-placeholder\">{{ placeholder() }}</span>\r\n }\r\n </span>\r\n <span class=\"skra-select-arrow\" aria-hidden=\"true\"></span>\r\n </button>\r\n\r\n <div\r\n #panel\r\n class=\"skra-select-listbox\"\r\n [id]=\"panelId\"\r\n role=\"listbox\"\r\n popover=\"auto\"\r\n (toggle)=\"onPanelToggle($event)\"\r\n >\r\n <ng-content></ng-content>\r\n </div>\r\n `,\r\n styleUrl: './select.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n providers: [{ provide: SKRA_LISTBOX_PANEL, useExisting: SkraSelect }],\r\n})\r\nexport class SkraSelect<T = unknown> extends _SkraSelectBase<T> {\r\n /** Consumer-owned value channel (ADR 037). */\r\n readonly value = input<T>();\r\n /** Fires ONLY on user selection — never on a programmatic `value()` change (ADR 037). */\r\n readonly valueChange = output<T>();\r\n /** Cardinality hint (ADR 0046) — single-select. */\r\n readonly multiple = false;\r\n\r\n /** The option whose value matches `value()` (ADR 037 selection model) — feeds the trigger. */\r\n protected readonly selectedOption = computed(\r\n () => this.options().find((o) => this.isSelected(o.value())) ?? null,\r\n );\r\n\r\n protected readonly triggerContext = computed(() => ({\r\n $implicit: this.value(),\r\n option: this.selectedOption(),\r\n }));\r\n\r\n // ── SkraListboxPanel seam ──\r\n isSelected(value: unknown): boolean {\r\n const current = this.value();\r\n return current !== undefined && this.compareWith()(current, value as T);\r\n }\r\n\r\n selectValue(value: unknown): void {\r\n this.valueChange.emit(value as T);\r\n this.close(); // single-select: pick + close\r\n }\r\n\r\n protected activeSeedItem(): SkraListboxKeyboardItem | null {\r\n return this.selectedOption();\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAgBA;;;;;;;;;;;;;;;AAeG;AAyDG,MAAO,UAAwB,SAAQ,eAAkB,CAAA;;AAEpD,IAAA,KAAK,GAAG,KAAK;yFAAK;;IAElB,WAAW,GAAG,MAAM,EAAK;;IAEzB,QAAQ,GAAG,KAAK;;AAGN,IAAA,cAAc,GAAG,QAAQ,CAC1C,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,IAAI;uFACrE;AAEkB,IAAA,cAAc,GAAG,QAAQ,CAAC,OAAO;AAClD,QAAA,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE;AACvB,QAAA,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE;KAC9B,CAAC;uFAAC;;AAGH,IAAA,UAAU,CAAC,KAAc,EAAA;AACvB,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE;AAC5B,QAAA,OAAO,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,KAAU,CAAC;IACzE;AAEA,IAAA,WAAW,CAAC,KAAc,EAAA;AACxB,QAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAU,CAAC;AACjC,QAAA,IAAI,CAAC,KAAK,EAAE,CAAC;IACf;IAEU,cAAc,GAAA;AACtB,QAAA,OAAO,IAAI,CAAC,cAAc,EAAE;IAC9B;uGA/BW,UAAU,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAV,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAU,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,WAAA,EAAA,aAAA,EAAA,EAAA,SAAA,EAFV,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,EAAA,QAAA,EAAA,CAAA,YAAA,CAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAjD3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,g/HAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EA9CS,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAoDf,UAAU,EAAA,UAAA,EAAA,CAAA;kBAxDtB,SAAS;+BACE,aAAa,EAAA,UAAA,EACX,IAAI,EAAA,QAAA,EACN,YAAY,WACb,CAAC,gBAAgB,CAAC,EAAA,QAAA,EACjB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CT,EAAA,CAAA,EAAA,eAAA,EAEgB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,SAAA,EAC9B,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAA,UAAY,EAAE,CAAC,EAAA,MAAA,EAAA,CAAA,g/HAAA,CAAA,EAAA;;;ACtFvE;;AAEG;;;;"}
@@ -28,27 +28,30 @@ import { input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component
28
28
  */
29
29
  class SkraSkeleton {
30
30
  /** The kind of content faked — selects placeholder geometry, never colour/theme (not a look-input). */
31
- shape = input('text', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
31
+ shape = input('text', /* @ts-ignore */
32
+ ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
32
33
  /** `text` only — number of line bars (multi-line paragraph). Ignored for `block`/`circle`. */
33
- lines = input(1, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
34
+ lines = input(1, /* @ts-ignore */
35
+ ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
34
36
  /** The bars to render: `lines` bars for text (≥1), a single bar for block/circle. */
35
37
  bars = computed(() => {
36
38
  const count = this.shape() === 'text' ? Math.max(1, Math.floor(this.lines())) : 1;
37
39
  return Array.from({ length: count }, (_, i) => i);
38
- }, ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
39
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
40
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: SkraSkeleton, isStandalone: true, selector: "skra-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-skra-shape": "shape()" } }, ngImport: i0, template: `
41
- @for (bar of bars(); track bar) {
42
- <span class="skra-skeleton__bar"></span>
43
- }
40
+ }, /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
42
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSkeleton, deps: [], target: i0.ɵɵFactoryTarget.Component });
43
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: SkraSkeleton, isStandalone: true, selector: "skra-skeleton", inputs: { shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-skra-shape": "shape()" } }, ngImport: i0, template: `
44
+ @for (bar of bars(); track bar) {
45
+ <span class="skra-skeleton__bar"></span>
46
+ }
44
47
  `, isInline: true, styles: [":host{display:block;inline-size:100%;margin:0}:host([data-skra-shape=text]){display:flex;flex-direction:column;gap:.5em}:host([data-skra-shape=block]),:host([data-skra-shape=circle]){block-size:100%}.skra-skeleton__bar{display:block;background-color:var(--skra-comp-skeleton-background);border-width:var(--skra-comp-skeleton-border-width);border-style:var(--skra-comp-skeleton-border-style);border-color:var(--skra-comp-skeleton-border-color);border-radius:var(--skra-comp-skeleton-radius);animation:skra-skeleton-pulse 1.4s var(--skra-comp-skeleton-motion-easing) infinite}:host([data-skra-shape=text]) .skra-skeleton__bar{block-size:.7em;inline-size:100%}:host([data-skra-shape=text]) .skra-skeleton__bar:last-child:not(:only-child){inline-size:60%}:host([data-skra-shape=block]) .skra-skeleton__bar,:host([data-skra-shape=circle]) .skra-skeleton__bar{block-size:100%;inline-size:100%}:host([data-skra-shape=circle]) .skra-skeleton__bar{border-radius:50%}@keyframes skra-skeleton-pulse{0%,to{opacity:1}50%{opacity:.7}}@media(prefers-reduced-motion:reduce){.skra-skeleton__bar{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
45
48
  }
46
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraSkeleton, decorators: [{
49
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSkeleton, decorators: [{
47
50
  type: Component,
48
- args: [{ selector: 'skra-skeleton', standalone: true, template: `
49
- @for (bar of bars(); track bar) {
50
- <span class="skra-skeleton__bar"></span>
51
- }
51
+ args: [{ selector: 'skra-skeleton', standalone: true, template: `
52
+ @for (bar of bars(); track bar) {
53
+ <span class="skra-skeleton__bar"></span>
54
+ }
52
55
  `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
53
56
  // Decorative — kept out of the accessibility tree (ADR 033 decorative exemption).
54
57
  'aria-hidden': 'true',
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-skeleton.mjs","sources":["../../../../libs/ui/skeleton/src/lib/skeleton/skeleton.ts","../../../../libs/ui/skeleton/src/skra-ui-skeleton.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n computed,\n input,\n} from '@angular/core';\n\nexport type SkraSkeletonShape = 'text' | 'block' | 'circle';\n\n/**\n * `<skra-skeleton>` — a decorative content placeholder (Wave 2, `libs/ui/indicators`).\n * Occupies the place of not-yet-loaded content with an animated, layout-shaped placeholder so\n * the layout does not shift on load (CLS) and the user perceives that content is coming.\n *\n * Third resident of the loading family, with a distinct SRP: `[skraLoadingIndicator]` (ADR 028)\n * is the *indeterminate* \"host is busy\" spinner; `SkraProgress` is the *determinate* \"task is\n * N% complete\" bar; **SkraSkeleton is the *content placeholder*** (\"the content hasn't arrived —\n * here is its shape\"). See `adr/specs/component-skeleton-spec.md` § SRP boundary.\n *\n * **A custom element, NOT a Component-on-native-host** (ADR 040): a skeleton has no semantic\n * native equivalent and is purely presentational, so it follows the custom-element atom model\n * (Badge/Chip/Alert precedent). The host is a layout container that renders its own inner\n * `.skra-skeleton__bar` element(s) — which is why a void-host directive was rejected: the\n * multi-line `text` shape needs internal structure.\n *\n * **Decorative a11y (ADR 033):** the host is `aria-hidden=\"true\"` — a skeleton conveys no\n * information to assistive tech (the decorative-icon exemption). The \"loading\" announcement\n * belongs to the consumer's content **container** (`aria-busy=\"true\"`), never to the skeleton.\n *\n * **Sizing is container/`em`-driven (ADR 021 — the CSP reason there are no `width`/`height`\n * inputs):** `text` line bars are `em`-relative (they inherit the surrounding font-size) at full\n * container width; `block`/`circle` fill a host the **consumer** sizes (e.g. via `[skraBox]`).\n * The host therefore never carries an inline `style`. State Surface all-N/A (non-interactive).\n */\n@Component({\n selector: 'skra-skeleton',\n standalone: true,\n template: `\n @for (bar of bars(); track bar) {\n <span class=\"skra-skeleton__bar\"></span>\n }\n `,\n styleUrl: './skeleton.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // Decorative — kept out of the accessibility tree (ADR 033 decorative exemption).\n 'aria-hidden': 'true',\n // Structural variant for CSP-safe SCSS branching (a variant, not an ADR 027 state).\n '[attr.data-skra-shape]': 'shape()',\n },\n})\nexport class SkraSkeleton {\n /** The kind of content faked — selects placeholder geometry, never colour/theme (not a look-input). */\n readonly shape = input<SkraSkeletonShape>('text');\n\n /** `text` only — number of line bars (multi-line paragraph). Ignored for `block`/`circle`. */\n readonly lines = input<number>(1);\n\n /** The bars to render: `lines` bars for text (≥1), a single bar for block/circle. */\n protected readonly bars = computed(() => {\n const count =\n this.shape() === 'text' ? Math.max(1, Math.floor(this.lines())) : 1;\n return Array.from({ length: count }, (_, i) => i);\n });\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;MAmBU,YAAY,CAAA;;AAEd,IAAA,KAAK,GAAG,KAAK,CAAoB,MAAM,4EAAC;;AAGxC,IAAA,KAAK,GAAG,KAAK,CAAS,CAAC,4EAAC;;AAGd,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAK;AACtC,QAAA,MAAM,KAAK,GACT,IAAI,CAAC,KAAK,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC;AACrE,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AACnD,IAAA,CAAC,2EAAC;uGAZS,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,UAAA,EAAA,EAAA,sBAAA,EAAA,SAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAfb;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,0kCAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAWU,YAAY,EAAA,UAAA,EAAA,CAAA;kBAlBxB,SAAS;+BACE,eAAe,EAAA,UAAA,EACb,IAAI,EAAA,QAAA,EACN;;;;AAIT,EAAA,CAAA,EAAA,eAAA,EAEgB,uBAAuB,CAAC,MAAM,iBAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,aAAa,EAAE,MAAM;;AAErB,wBAAA,wBAAwB,EAAE,SAAS;AACpC,qBAAA,EAAA,MAAA,EAAA,CAAA,0kCAAA,CAAA,EAAA;;;ACnDH;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-skeleton.mjs","sources":["../../../../libs/ui/skeleton/src/lib/skeleton/skeleton.ts","../../../../libs/ui/skeleton/src/skra-ui-skeleton.ts"],"sourcesContent":["import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ViewEncapsulation,\r\n computed,\r\n input,\r\n} from '@angular/core';\r\n\r\nexport type SkraSkeletonShape = 'text' | 'block' | 'circle';\r\n\r\n/**\r\n * `<skra-skeleton>` — a decorative content placeholder (Wave 2, `libs/ui/indicators`).\r\n * Occupies the place of not-yet-loaded content with an animated, layout-shaped placeholder so\r\n * the layout does not shift on load (CLS) and the user perceives that content is coming.\r\n *\r\n * Third resident of the loading family, with a distinct SRP: `[skraLoadingIndicator]` (ADR 028)\r\n * is the *indeterminate* \"host is busy\" spinner; `SkraProgress` is the *determinate* \"task is\r\n * N% complete\" bar; **SkraSkeleton is the *content placeholder*** (\"the content hasn't arrived —\r\n * here is its shape\"). See `adr/specs/component-skeleton-spec.md` § SRP boundary.\r\n *\r\n * **A custom element, NOT a Component-on-native-host** (ADR 040): a skeleton has no semantic\r\n * native equivalent and is purely presentational, so it follows the custom-element atom model\r\n * (Badge/Chip/Alert precedent). The host is a layout container that renders its own inner\r\n * `.skra-skeleton__bar` element(s) — which is why a void-host directive was rejected: the\r\n * multi-line `text` shape needs internal structure.\r\n *\r\n * **Decorative a11y (ADR 033):** the host is `aria-hidden=\"true\"` — a skeleton conveys no\r\n * information to assistive tech (the decorative-icon exemption). The \"loading\" announcement\r\n * belongs to the consumer's content **container** (`aria-busy=\"true\"`), never to the skeleton.\r\n *\r\n * **Sizing is container/`em`-driven (ADR 021 — the CSP reason there are no `width`/`height`\r\n * inputs):** `text` line bars are `em`-relative (they inherit the surrounding font-size) at full\r\n * container width; `block`/`circle` fill a host the **consumer** sizes (e.g. via `[skraBox]`).\r\n * The host therefore never carries an inline `style`. State Surface all-N/A (non-interactive).\r\n */\r\n@Component({\r\n selector: 'skra-skeleton',\r\n standalone: true,\r\n template: `\r\n @for (bar of bars(); track bar) {\r\n <span class=\"skra-skeleton__bar\"></span>\r\n }\r\n `,\r\n styleUrl: './skeleton.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n host: {\r\n // Decorative — kept out of the accessibility tree (ADR 033 decorative exemption).\r\n 'aria-hidden': 'true',\r\n // Structural variant for CSP-safe SCSS branching (a variant, not an ADR 027 state).\r\n '[attr.data-skra-shape]': 'shape()',\r\n },\r\n})\r\nexport class SkraSkeleton {\r\n /** The kind of content faked — selects placeholder geometry, never colour/theme (not a look-input). */\r\n readonly shape = input<SkraSkeletonShape>('text');\r\n\r\n /** `text` only — number of line bars (multi-line paragraph). Ignored for `block`/`circle`. */\r\n readonly lines = input<number>(1);\r\n\r\n /** The bars to render: `lines` bars for text (≥1), a single bar for block/circle. */\r\n protected readonly bars = computed(() => {\r\n const count =\r\n this.shape() === 'text' ? Math.max(1, Math.floor(this.lines())) : 1;\r\n return Array.from({ length: count }, (_, i) => i);\r\n });\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAUA;;;;;;;;;;;;;;;;;;;;;;;;AAwBG;MAmBU,YAAY,CAAA;;IAEd,KAAK,GAAG,KAAK,CAAoB,MAAM;8EAAC;;IAGxC,KAAK,GAAG,KAAK,CAAS,CAAC;8EAAC;;AAGd,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAK;AACtC,QAAA,MAAM,KAAK,GACT,IAAI,CAAC,KAAK,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC;AACrE,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;6EAAC;uGAZS,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,UAAA,EAAA,EAAA,sBAAA,EAAA,SAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAfb,CAAA;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,0kCAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAWU,YAAY,EAAA,UAAA,EAAA,CAAA;kBAlBxB,SAAS;+BACE,eAAe,EAAA,UAAA,EACb,IAAI,EAAA,QAAA,EACN,CAAA;;;;AAIT,EAAA,CAAA,EAAA,eAAA,EAEgB,uBAAuB,CAAC,MAAM,iBAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,aAAa,EAAE,MAAM;;AAErB,wBAAA,wBAAwB,EAAE,SAAS;AACpC,qBAAA,EAAA,MAAA,EAAA,CAAA,0kCAAA,CAAA,EAAA;;;ACnDH;;AAEG;;;;"}