@skra/ui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +7 -0
  3. package/README.md +39 -0
  4. package/consumer-guide/README.md +84 -0
  5. package/consumer-guide/accessible-names.md +80 -0
  6. package/consumer-guide/forms-and-binding.md +149 -0
  7. package/consumer-guide/layout.md +86 -0
  8. package/consumer-guide/overlays.md +100 -0
  9. package/consumer-guide/setup-and-gotchas.md +103 -0
  10. package/consumer-guide/theming-and-encapsulation.md +115 -0
  11. package/consumer-guide/wrapping-patterns.md +90 -0
  12. package/fesm2022/skra-ui-accordion.mjs +333 -0
  13. package/fesm2022/skra-ui-accordion.mjs.map +1 -0
  14. package/fesm2022/skra-ui-alert.mjs +110 -0
  15. package/fesm2022/skra-ui-alert.mjs.map +1 -0
  16. package/fesm2022/skra-ui-avatar.mjs +83 -0
  17. package/fesm2022/skra-ui-avatar.mjs.map +1 -0
  18. package/fesm2022/skra-ui-badge.mjs +91 -0
  19. package/fesm2022/skra-ui-badge.mjs.map +1 -0
  20. package/fesm2022/skra-ui-button.mjs +67 -0
  21. package/fesm2022/skra-ui-button.mjs.map +1 -0
  22. package/fesm2022/skra-ui-checkbox.mjs +98 -0
  23. package/fesm2022/skra-ui-checkbox.mjs.map +1 -0
  24. package/fesm2022/skra-ui-chip.mjs +82 -0
  25. package/fesm2022/skra-ui-chip.mjs.map +1 -0
  26. package/fesm2022/skra-ui-combobox.mjs +335 -0
  27. package/fesm2022/skra-ui-combobox.mjs.map +1 -0
  28. package/fesm2022/skra-ui-core.mjs +529 -0
  29. package/fesm2022/skra-ui-core.mjs.map +1 -0
  30. package/fesm2022/skra-ui-counter.mjs +53 -0
  31. package/fesm2022/skra-ui-counter.mjs.map +1 -0
  32. package/fesm2022/skra-ui-datepicker.mjs +867 -0
  33. package/fesm2022/skra-ui-datepicker.mjs.map +1 -0
  34. package/fesm2022/skra-ui-dialog.mjs +133 -0
  35. package/fesm2022/skra-ui-dialog.mjs.map +1 -0
  36. package/fesm2022/skra-ui-field.mjs +293 -0
  37. package/fesm2022/skra-ui-field.mjs.map +1 -0
  38. package/fesm2022/skra-ui-icon.mjs +54 -0
  39. package/fesm2022/skra-ui-icon.mjs.map +1 -0
  40. package/fesm2022/skra-ui-input.mjs +42 -0
  41. package/fesm2022/skra-ui-input.mjs.map +1 -0
  42. package/fesm2022/skra-ui-kbd.mjs +44 -0
  43. package/fesm2022/skra-ui-kbd.mjs.map +1 -0
  44. package/fesm2022/skra-ui-layout.mjs +119 -0
  45. package/fesm2022/skra-ui-layout.mjs.map +1 -0
  46. package/fesm2022/skra-ui-link.mjs +29 -0
  47. package/fesm2022/skra-ui-link.mjs.map +1 -0
  48. package/fesm2022/skra-ui-loading-indicator.mjs +24 -0
  49. package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -0
  50. package/fesm2022/skra-ui-menu.mjs +334 -0
  51. package/fesm2022/skra-ui-menu.mjs.map +1 -0
  52. package/fesm2022/skra-ui-multi-select.mjs +179 -0
  53. package/fesm2022/skra-ui-multi-select.mjs.map +1 -0
  54. package/fesm2022/skra-ui-native-select.mjs +123 -0
  55. package/fesm2022/skra-ui-native-select.mjs.map +1 -0
  56. package/fesm2022/skra-ui-popover.mjs +158 -0
  57. package/fesm2022/skra-ui-popover.mjs.map +1 -0
  58. package/fesm2022/skra-ui-progress.mjs +57 -0
  59. package/fesm2022/skra-ui-progress.mjs.map +1 -0
  60. package/fesm2022/skra-ui-radio.mjs +92 -0
  61. package/fesm2022/skra-ui-radio.mjs.map +1 -0
  62. package/fesm2022/skra-ui-rating-display.mjs +81 -0
  63. package/fesm2022/skra-ui-rating-display.mjs.map +1 -0
  64. package/fesm2022/skra-ui-select.mjs +151 -0
  65. package/fesm2022/skra-ui-select.mjs.map +1 -0
  66. package/fesm2022/skra-ui-skeleton.mjs +65 -0
  67. package/fesm2022/skra-ui-skeleton.mjs.map +1 -0
  68. package/fesm2022/skra-ui-star-rating.mjs +110 -0
  69. package/fesm2022/skra-ui-star-rating.mjs.map +1 -0
  70. package/fesm2022/skra-ui-status-dot.mjs +61 -0
  71. package/fesm2022/skra-ui-status-dot.mjs.map +1 -0
  72. package/fesm2022/skra-ui-switch.mjs +94 -0
  73. package/fesm2022/skra-ui-switch.mjs.map +1 -0
  74. package/fesm2022/skra-ui-tabs.mjs +356 -0
  75. package/fesm2022/skra-ui-tabs.mjs.map +1 -0
  76. package/fesm2022/skra-ui-textarea.mjs +81 -0
  77. package/fesm2022/skra-ui-textarea.mjs.map +1 -0
  78. package/fesm2022/skra-ui-toast.mjs +208 -0
  79. package/fesm2022/skra-ui-toast.mjs.map +1 -0
  80. package/fesm2022/skra-ui-tooltip.mjs +252 -0
  81. package/fesm2022/skra-ui-tooltip.mjs.map +1 -0
  82. package/fesm2022/skra-ui-typography.mjs +35 -0
  83. package/fesm2022/skra-ui-typography.mjs.map +1 -0
  84. package/fesm2022/skra-ui.mjs +4 -0
  85. package/fesm2022/skra-ui.mjs.map +1 -0
  86. package/package.json +175 -0
  87. package/types/skra-ui-accordion.d.ts +196 -0
  88. package/types/skra-ui-accordion.d.ts.map +1 -0
  89. package/types/skra-ui-alert.d.ts +81 -0
  90. package/types/skra-ui-alert.d.ts.map +1 -0
  91. package/types/skra-ui-avatar.d.ts +71 -0
  92. package/types/skra-ui-avatar.d.ts.map +1 -0
  93. package/types/skra-ui-badge.d.ts +73 -0
  94. package/types/skra-ui-badge.d.ts.map +1 -0
  95. package/types/skra-ui-button.d.ts +45 -0
  96. package/types/skra-ui-button.d.ts.map +1 -0
  97. package/types/skra-ui-checkbox.d.ts +52 -0
  98. package/types/skra-ui-checkbox.d.ts.map +1 -0
  99. package/types/skra-ui-chip.d.ts +60 -0
  100. package/types/skra-ui-chip.d.ts.map +1 -0
  101. package/types/skra-ui-combobox.d.ts +95 -0
  102. package/types/skra-ui-combobox.d.ts.map +1 -0
  103. package/types/skra-ui-core.d.ts +314 -0
  104. package/types/skra-ui-core.d.ts.map +1 -0
  105. package/types/skra-ui-counter.d.ts +33 -0
  106. package/types/skra-ui-counter.d.ts.map +1 -0
  107. package/types/skra-ui-datepicker.d.ts +164 -0
  108. package/types/skra-ui-datepicker.d.ts.map +1 -0
  109. package/types/skra-ui-dialog.d.ts +59 -0
  110. package/types/skra-ui-dialog.d.ts.map +1 -0
  111. package/types/skra-ui-field.d.ts +181 -0
  112. package/types/skra-ui-field.d.ts.map +1 -0
  113. package/types/skra-ui-icon.d.ts +38 -0
  114. package/types/skra-ui-icon.d.ts.map +1 -0
  115. package/types/skra-ui-input.d.ts +22 -0
  116. package/types/skra-ui-input.d.ts.map +1 -0
  117. package/types/skra-ui-kbd.d.ts +33 -0
  118. package/types/skra-ui-kbd.d.ts.map +1 -0
  119. package/types/skra-ui-layout.d.ts +86 -0
  120. package/types/skra-ui-layout.d.ts.map +1 -0
  121. package/types/skra-ui-link.d.ts +20 -0
  122. package/types/skra-ui-link.d.ts.map +1 -0
  123. package/types/skra-ui-loading-indicator.d.ts +9 -0
  124. package/types/skra-ui-loading-indicator.d.ts.map +1 -0
  125. package/types/skra-ui-menu.d.ts +116 -0
  126. package/types/skra-ui-menu.d.ts.map +1 -0
  127. package/types/skra-ui-multi-select.d.ts +47 -0
  128. package/types/skra-ui-multi-select.d.ts.map +1 -0
  129. package/types/skra-ui-native-select.d.ts +74 -0
  130. package/types/skra-ui-native-select.d.ts.map +1 -0
  131. package/types/skra-ui-popover.d.ts +57 -0
  132. package/types/skra-ui-popover.d.ts.map +1 -0
  133. package/types/skra-ui-progress.d.ts +41 -0
  134. package/types/skra-ui-progress.d.ts.map +1 -0
  135. package/types/skra-ui-radio.d.ts +54 -0
  136. package/types/skra-ui-radio.d.ts.map +1 -0
  137. package/types/skra-ui-rating-display.d.ts +56 -0
  138. package/types/skra-ui-rating-display.d.ts.map +1 -0
  139. package/types/skra-ui-select.d.ts +43 -0
  140. package/types/skra-ui-select.d.ts.map +1 -0
  141. package/types/skra-ui-skeleton.d.ts +42 -0
  142. package/types/skra-ui-skeleton.d.ts.map +1 -0
  143. package/types/skra-ui-star-rating.d.ts +71 -0
  144. package/types/skra-ui-star-rating.d.ts.map +1 -0
  145. package/types/skra-ui-status-dot.d.ts +62 -0
  146. package/types/skra-ui-status-dot.d.ts.map +1 -0
  147. package/types/skra-ui-switch.d.ts +54 -0
  148. package/types/skra-ui-switch.d.ts.map +1 -0
  149. package/types/skra-ui-tabs.d.ts +191 -0
  150. package/types/skra-ui-tabs.d.ts.map +1 -0
  151. package/types/skra-ui-textarea.d.ts +55 -0
  152. package/types/skra-ui-textarea.d.ts.map +1 -0
  153. package/types/skra-ui-toast.d.ts +140 -0
  154. package/types/skra-ui-toast.d.ts.map +1 -0
  155. package/types/skra-ui-tooltip.d.ts +90 -0
  156. package/types/skra-ui-tooltip.d.ts.map +1 -0
  157. package/types/skra-ui-typography.d.ts +17 -0
  158. package/types/skra-ui-typography.d.ts.map +1 -0
  159. package/types/skra-ui.d.ts +3 -0
  160. package/types/skra-ui.d.ts.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skra-ui-status-dot.mjs","sources":["../../../../libs/ui/status-dot/src/lib/status-dot/status-dot.ts","../../../../libs/ui/status-dot/src/skra-ui-status-dot.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\n/**\n * StatusDotIntent — the Variant Surface Intent axis (ADR 035 §3.1, the status palette subset).\n * Drops the action intents `primary`/`secondary` (those belong to interactive surfaces, not a\n * status glyph). Closed enum; extension requires an ADR 035 amendment.\n */\nexport type StatusDotIntent =\n | 'neutral'\n | 'info'\n | 'success'\n | 'warning'\n | 'danger';\n\n/**\n * StatusDotSize — the Variant Surface Size axis (ADR 035 §3.1, downward subset `sm|md|lg`).\n * `xs`/`xl` are reserved (ADR 035 §3.2) and NOT in MVP — adding them later is purely additive.\n * Unlike content-width Badge (which has no size — its size is its text), the dot has NO content,\n * so size is a genuine free variable the consumer needs (corner-of-`lg`-avatar vs inline-in-text).\n */\nexport type StatusDotSize = 'sm' | 'md' | 'lg';\n\n/**\n * `<skra-status-dot>` — a bare, small, non-interactive status/presence indicator (a coloured dot)\n * composed beside other content: an avatar corner (online/away/busy/offline), or beside a label.\n * Second resident of `libs/ui/data-display`, sibling to `<skra-avatar>` (whose corner presence-dot\n * it provides). See `adr/specs/component-status-dot-spec.md`.\n *\n * **Element, not a decorated native (ADR 012):** there is no native `<dot>`, and a bare `<span>`\n * carries no semantics and forces a colour literal. NOT a Web Component (ADR 000).\n *\n * **No content (the SRP line vs `skra-badge`):** the dot IS the glyph — no slots, no projected\n * content, no label/count. Meaning is carried by ADJACENT text the consumer owns. The defining\n * value-add over a hand-rolled span is the semantic `intent`→token-colour mapping (no hex — ADR\n * 002/023) + the size axis that tracks the avatar.\n *\n * **Non-interactive (Avatar/Badge precedent):** no focusable node, no widget role, no keyboard, no\n * outputs → State Surface all-N/A. A clickable status = `button[skraButton]` composition, not a\n * variant.\n *\n * **Accessible name (ADR 033 Path 3 — graphic host, the SkraIcon/Avatar contract):** the host\n * carries NO default ARIA. The consumer declares semantics explicitly, exactly as for\n * `<skra-icon>`/`<skra-avatar>`:\n * - decorative (Mode A, the ~90% case — dot beside visible/sr-only status text) → `aria-hidden=\"true\"`;\n * - meaningful (Mode B — the dot is the SOLE status carrier) → **`role=\"img\"` + `aria-label`/\n * `aria-labelledby` TOGETHER**. `role=\"img\"` is REQUIRED: `aria-label` on the roleless host is\n * ARIA-prohibited (axe `aria-prohibited-attr`, the Avatar Mode-B fix) and conveys no name.\n * WCAG 1.4.1 (colour-independence) cannot be enforced by the atom — the adjacent text is the\n * consumer's; the `InlineStatus`/`A11yCheck` stories carry the editorial contract. NO `ariaLabel`\n * input (ADR 033 boundary #8).\n *\n * **Look is skin-owned:** circle-vs-square is the `radius` comp token (brutal's `radius.full: 0`\n * ships a square dot automatically). The fill is the semantic `intent`→`sys.color.<intent>.base`\n * (recolour at the sys level, not a per-dot token — skin philosophy); NO `emphasis` (always filled),\n * NO `live` (a dot has no text to announce — the consumer's adjacent live text does), NO\n * `color`/`variant`/`shape` look-inputs.\n */\n@Component({\n selector: 'skra-status-dot',\n standalone: true,\n template: '',\n styleUrl: './status-dot.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // Variant Surface reflection (ADR 035 §3.5; ADR 021 — never [style.--var]).\n '[attr.data-skra-intent]': 'intent()',\n '[attr.data-skra-size]': 'size()',\n },\n})\nexport class SkraStatusDot {\n /** Variant Surface Intent axis (ADR 035) — the fill colour (status palette subset). */\n readonly intent = input<StatusDotIntent>('neutral');\n\n /** Variant Surface Size axis (ADR 035) — the dot's (colored core) diameter. */\n readonly size = input<StatusDotSize>('md');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AA2BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCG;MAcU,aAAa,CAAA;;AAEf,IAAA,MAAM,GAAG,KAAK,CAAkB,SAAS,6EAAC;;AAG1C,IAAA,IAAI,GAAG,KAAK,CAAgB,IAAI,2EAAC;uGAL/B,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,aAAa,uaAVd,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,gvCAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAUD,aAAa,EAAA,UAAA,EAAA,CAAA;kBAbzB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,EAAA,UAAA,EACf,IAAI,EAAA,QAAA,EACN,EAAE,EAAA,eAAA,EAEK,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,yBAAyB,EAAE,UAAU;AACrC,wBAAA,uBAAuB,EAAE,QAAQ;AAClC,qBAAA,EAAA,MAAA,EAAA,CAAA,gvCAAA,CAAA,EAAA;;;ACzEH;;AAEG;;;;"}
@@ -0,0 +1,94 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ElementRef, effect, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { SkraFieldContext } from '@skra/ui/field';
4
+
5
+ /**
6
+ * Wires a native `<input type="checkbox">` to the **switch** role + skin when inside
7
+ * `<skra-field>` (ADR 013), or standalone with no cascade. Per ADR 039/040, Forge ships
8
+ * no `<skra-switch>` wrapper — this is a Component-on-the-native-host (`<input>` is void →
9
+ * `template: ''`, ADR 040 §2) carrying its own `switch.scss` via Emulated encapsulation. The
10
+ * ADR 039 revision history named this exact pattern as the future switch primitive.
11
+ *
12
+ * **The directive SETS `role="switch"`** (it is not part of the selector) — the consumer must
13
+ * not be asked to repeat an obvious, required ARIA role. No native HTML element produces switch
14
+ * semantics, so `role="switch"` is the legitimate ARIA mechanism (native-attr-first rule 2). The
15
+ * browser maps the native checked state to `aria-checked` (on/off) under this role.
16
+ *
17
+ * <!-- inside Field -->
18
+ * <skra-field>
19
+ * <input type="checkbox" skraSwitch [checked]="x()" (change)="x.set($any($event.target).checked)" />
20
+ * <label skraLabelFor>Enable notifications</label>
21
+ * </skra-field>
22
+ *
23
+ * <!-- standalone (label wraps input — no id/for needed) -->
24
+ * <label>
25
+ * <input type="checkbox" skraSwitch [checked]="x()" (change)="x.set($any($event.target).checked)" />
26
+ * Dark mode
27
+ * </label>
28
+ *
29
+ * Value channel stays consumer-owned per ADR 037 (native `[checked]` + `(change)`, Reactive
30
+ * Forms `formControlName`, Signal Forms `[formField]`, or `[(ngModel)]` — all orthogonal). Toggle
31
+ * state is NOT reflected as `data-skra-*` (that would require Forge to observe native state,
32
+ * violating ADR 037); the skin keys off the native `:checked` pseudo-class.
33
+ *
34
+ * Distinct from `skraCheckbox` (the atom-granularity split): a switch is strictly binary —
35
+ * `role="switch"` forbids `aria-checked="mixed"`, so there is **no `indeterminate` input**.
36
+ */
37
+ class SkraSwitch {
38
+ el = inject(ElementRef);
39
+ context = inject(SkraFieldContext, { optional: true });
40
+ constructor() {
41
+ // Field-cascaded native properties — only bind when context is present so
42
+ // standalone consumers retain full control of their static attributes.
43
+ if (this.context) {
44
+ const ctx = this.context;
45
+ const el = this.el.nativeElement;
46
+ effect(() => {
47
+ el.id = ctx.fieldId();
48
+ });
49
+ effect(() => {
50
+ el.disabled = ctx.isDisabled();
51
+ });
52
+ }
53
+ }
54
+ /**
55
+ * Readonly enforcement — native checkbox has no `[readonly]`, so without this the
56
+ * `aria-readonly` we announce would be a lie (the user could still toggle it).
57
+ * `preventDefault` on `click` blocks the toggle from both pointer and Space (keyboard
58
+ * activation dispatches a click on form controls). Mirrors the checkbox/radio guards.
59
+ */
60
+ blockWhenReadonly(event) {
61
+ if (this.context?.isReadonly())
62
+ event.preventDefault();
63
+ }
64
+ /** Block keyboard activation (Space) when readonly; let `Tab` through. */
65
+ blockKeyWhenReadonly(event) {
66
+ if (this.context?.isReadonly() && event.key !== 'Tab') {
67
+ event.preventDefault();
68
+ }
69
+ }
70
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
71
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: SkraSwitch, isStandalone: true, selector: "input[type=\"checkbox\"][skraSwitch]", host: { attributes: { "role": "switch" }, 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;box-sizing:border-box;flex-shrink:0;cursor:pointer;outline:none;inline-size:var(--skra-comp-field-switch-track-inline-size);block-size:var(--skra-comp-field-switch-track-block-size);border-width:var(--skra-comp-field-switch-border-width);border-style:solid;border-color:var(--skra-comp-field-switch-border-color);border-radius:var(--skra-comp-field-switch-track-radius);background-color:var(--skra-comp-field-switch-background);--_switch-thumb: var(--skra-comp-field-switch-thumb-color);background-repeat:no-repeat;background-size:var(--skra-comp-field-switch-thumb-size) var(--skra-comp-field-switch-thumb-size);background-position:left var(--skra-comp-field-switch-thumb-inset) center;background-image:radial-gradient(circle,var(--_switch-thumb) 49%,transparent 50%);transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),background-position 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){--_switch-thumb: var(--skra-comp-field-switch-thumb-color-checked);background-color:var(--skra-comp-field-switch-checked-background);border-color:var(--skra-comp-field-switch-checked-background);background-position:right var(--skra-comp-field-switch-thumb-inset) center}:host(:hover:not(:disabled):not([aria-readonly=true])){background-color:var(--skra-comp-field-switch-background-hover);border-color:var(--skra-comp-field-switch-border-color-hover)}:host(:checked:hover:not(:disabled):not([aria-readonly=true])){background-color:var(--skra-comp-field-switch-checked-background-hover);border-color:var(--skra-comp-field-switch-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-switch-checked-background-readonly);border-color:var(--skra-comp-field-switch-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 });
72
+ }
73
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraSwitch, decorators: [{
74
+ type: Component,
75
+ args: [{ selector: 'input[type="checkbox"][skraSwitch]', standalone: true, template: '', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
76
+ // The directive owns the switch semantic (resolved in scope, refines the ADR 039 sketch).
77
+ role: 'switch',
78
+ '[attr.data-skra-disabled]': 'context?.isDisabled() ? "" : null',
79
+ '[attr.aria-invalid]': 'context?.hasError() ? "true" : null',
80
+ // Readonly is ARIA-only (native `readonly` doesn't apply to checkboxes per HTML spec);
81
+ // styling keys off `[aria-readonly]`. Mirrors skraCheckbox / skraRadio.
82
+ '[attr.aria-readonly]': 'context?.isReadonly() ? "true" : null',
83
+ '[attr.aria-describedby]': 'context?.describedByIds() ?? null',
84
+ '(click)': 'blockWhenReadonly($event)',
85
+ '(keydown)': 'blockKeyWhenReadonly($event)',
86
+ }, styles: [":host{appearance:none;box-sizing:border-box;flex-shrink:0;cursor:pointer;outline:none;inline-size:var(--skra-comp-field-switch-track-inline-size);block-size:var(--skra-comp-field-switch-track-block-size);border-width:var(--skra-comp-field-switch-border-width);border-style:solid;border-color:var(--skra-comp-field-switch-border-color);border-radius:var(--skra-comp-field-switch-track-radius);background-color:var(--skra-comp-field-switch-background);--_switch-thumb: var(--skra-comp-field-switch-thumb-color);background-repeat:no-repeat;background-size:var(--skra-comp-field-switch-thumb-size) var(--skra-comp-field-switch-thumb-size);background-position:left var(--skra-comp-field-switch-thumb-inset) center;background-image:radial-gradient(circle,var(--_switch-thumb) 49%,transparent 50%);transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),background-position 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){--_switch-thumb: var(--skra-comp-field-switch-thumb-color-checked);background-color:var(--skra-comp-field-switch-checked-background);border-color:var(--skra-comp-field-switch-checked-background);background-position:right var(--skra-comp-field-switch-thumb-inset) center}:host(:hover:not(:disabled):not([aria-readonly=true])){background-color:var(--skra-comp-field-switch-background-hover);border-color:var(--skra-comp-field-switch-border-color-hover)}:host(:checked:hover:not(:disabled):not([aria-readonly=true])){background-color:var(--skra-comp-field-switch-checked-background-hover);border-color:var(--skra-comp-field-switch-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-switch-checked-background-readonly);border-color:var(--skra-comp-field-switch-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"] }]
87
+ }], ctorParameters: () => [] });
88
+
89
+ /**
90
+ * Generated bundle index. Do not edit.
91
+ */
92
+
93
+ export { SkraSwitch };
94
+ //# sourceMappingURL=skra-ui-switch.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skra-ui-switch.mjs","sources":["../../../../libs/ui/switch/src/lib/switch/switch.ts","../../../../libs/ui/switch/src/skra-ui-switch.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=\"checkbox\">` to the **switch** role + skin when inside\n * `<skra-field>` (ADR 013), or standalone with no cascade. Per ADR 039/040, Forge ships\n * no `<skra-switch>` wrapper — this is a Component-on-the-native-host (`<input>` is void →\n * `template: ''`, ADR 040 §2) carrying its own `switch.scss` via Emulated encapsulation. The\n * ADR 039 revision history named this exact pattern as the future switch primitive.\n *\n * **The directive SETS `role=\"switch\"`** (it is not part of the selector) — the consumer must\n * not be asked to repeat an obvious, required ARIA role. No native HTML element produces switch\n * semantics, so `role=\"switch\"` is the legitimate ARIA mechanism (native-attr-first rule 2). The\n * browser maps the native checked state to `aria-checked` (on/off) under this role.\n *\n * <!-- inside Field -->\n * <skra-field>\n * <input type=\"checkbox\" skraSwitch [checked]=\"x()\" (change)=\"x.set($any($event.target).checked)\" />\n * <label skraLabelFor>Enable notifications</label>\n * </skra-field>\n *\n * <!-- standalone (label wraps input — no id/for needed) -->\n * <label>\n * <input type=\"checkbox\" skraSwitch [checked]=\"x()\" (change)=\"x.set($any($event.target).checked)\" />\n * Dark mode\n * </label>\n *\n * Value channel stays consumer-owned per ADR 037 (native `[checked]` + `(change)`, Reactive\n * Forms `formControlName`, Signal Forms `[formField]`, or `[(ngModel)]` — all orthogonal). Toggle\n * state is NOT reflected as `data-skra-*` (that would require Forge to observe native state,\n * violating ADR 037); the skin keys off the native `:checked` pseudo-class.\n *\n * Distinct from `skraCheckbox` (the atom-granularity split): a switch is strictly binary —\n * `role=\"switch\"` forbids `aria-checked=\"mixed\"`, so there is **no `indeterminate` input**.\n */\n@Component({\n selector: 'input[type=\"checkbox\"][skraSwitch]',\n standalone: true,\n // Void host — no <ng-content>. See ADR 040 §2.\n template: '',\n styleUrl: './switch.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // The directive owns the switch semantic (resolved in scope, refines the ADR 039 sketch).\n role: 'switch',\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 checkboxes per HTML spec);\n // styling keys off `[aria-readonly]`. Mirrors skraCheckbox / skraRadio.\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 SkraSwitch {\n private readonly el = inject<ElementRef<HTMLInputElement>>(ElementRef);\n readonly context = inject(SkraFieldContext, { optional: true });\n\n constructor() {\n // Field-cascaded native properties — only bind when context is present so\n // standalone consumers retain full control of their static attributes.\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 checkbox has no `[readonly]`, so without this the\n * `aria-readonly` we announce would be a lie (the user could still toggle it).\n * `preventDefault` on `click` blocks the toggle from both pointer and Space (keyboard\n * activation dispatches a click on form controls). Mirrors the checkbox/radio guards.\n */\n protected blockWhenReadonly(event: Event): void {\n if (this.context?.isReadonly()) event.preventDefault();\n }\n\n /** Block keyboard activation (Space) when readonly; let `Tab` through. */\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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BG;MAsBU,UAAU,CAAA;AACJ,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;;;AAGE,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;;;;;AAKG;AACO,IAAA,iBAAiB,CAAC,KAAY,EAAA;AACtC,QAAA,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE;YAAE,KAAK,CAAC,cAAc,EAAE;IACxD;;AAGU,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;uGAlCW,UAAU,EAAA,IAAA,EAAA,EAAA,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,2fAjBX,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,qnFAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAiBD,UAAU,EAAA,UAAA,EAAA,CAAA;kBArBtB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,oCAAoC,EAAA,UAAA,EAClC,IAAI,EAAA,QAAA,EAEN,EAAE,EAAA,eAAA,EAEK,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;;AAEJ,wBAAA,IAAI,EAAE,QAAQ;AACd,wBAAA,2BAA2B,EAAE,mCAAmC;AAChE,wBAAA,qBAAqB,EAAE,qCAAqC;;;AAG5D,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,qnFAAA,CAAA,EAAA;;;AC7DH;;AAEG;;;;"}
@@ -0,0 +1,356 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, Injectable, input, inject, ElementRef, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, output, contentChildren, effect, DestroyRef, Directive } from '@angular/core';
3
+ import { FocusKeyManager } from '@angular/cdk/a11y';
4
+
5
+ /** Module-global counter → a deterministic, unique id base per `[skraTabs]` instance.
6
+ * Equivalent to the forms `IdGeneratorService` (ADR 013) but lib-local, so `@skra/navigation`
7
+ * takes no dependency on `@skra/forms` just for id generation. Stable across SSR↔hydration
8
+ * (same instantiation order yields the same ids — the standard counter technique). */
9
+ let tabsInstanceCounter = 0;
10
+ /** Sanitize a consumer `value` into an id-safe fragment (no spaces / special chars). */
11
+ function idSafe(value) {
12
+ return value.replace(/[^a-zA-Z0-9_-]/g, '-');
13
+ }
14
+ /**
15
+ * DI Context provided per `[skraTabs]` instance — the ADR 013 §3.6 **non-form** Contextual-ID-Sync
16
+ * bus wiring `tab` ↔ `tabpanel`. It carries **IDs + derived, read-only interaction state only**
17
+ * (`selected` mirrors the consumer's `[selected]`; `activeValue` is the roving-focus position). It
18
+ * **never owns value** — selection stays consumer-owned (ADR 037); a tab requests a change through
19
+ * `select()`, which the coordinator implements and from which it emits `(selectedChange)`.
20
+ */
21
+ class SkraTabsContext {
22
+ /** Unique per `[skraTabs]` — the prefix for every tab/panel id in this group. */
23
+ baseId = `skra-tabs-${++tabsInstanceCounter}`;
24
+ /** Mirror of the consumer's `[selected]` — drives `aria-selected` + panel visibility (read-only to children). */
25
+ selected = signal('', ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
26
+ /** Roving-focus position — the value of the tab that currently carries `tabindex="0"` (read-only to children). */
27
+ activeValue = signal('', ...(ngDevMode ? [{ debugName: "activeValue" }] : /* istanbul ignore next */ []));
28
+ /** Activation model (read-only to children). */
29
+ activation = signal('automatic', ...(ngDevMode ? [{ debugName: "activation" }] : /* istanbul ignore next */ []));
30
+ tabId(value) {
31
+ return `${this.baseId}-tab-${idSafe(value)}`;
32
+ }
33
+ panelId(value) {
34
+ return `${this.baseId}-panel-${idSafe(value)}`;
35
+ }
36
+ isSelected(value) {
37
+ return this.selected() === value;
38
+ }
39
+ /** Coordinator-assigned: a tab/keyboard requests selection (the coordinator emits `(selectedChange)`). */
40
+ select = () => undefined;
41
+ /** Coordinator-assigned: the tablist forwards its `keydown` here (routed to the keyboard controller). */
42
+ handleKeydown = () => undefined;
43
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabsContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
44
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabsContext });
45
+ }
46
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabsContext, decorators: [{
47
+ type: Injectable
48
+ }] });
49
+
50
+ /**
51
+ * SkraTab — a single tab, a Component on the native command host `button[skraTab]` (ADR 012/040:
52
+ * a `<button>` gives Enter/Space→click + focusability for free; its implicit role is overridden to
53
+ * `role="tab"`, the precedent set by `button[skraMenuItem]`).
54
+ *
55
+ * Roving tabindex: `tabindex="0"` only when this tab is the roving stop (`SkraTabsContext.activeValue`),
56
+ * else `-1` — the controller (`SkraTabsKeyboard`) moves real focus. `aria-selected` reflects the
57
+ * consumer-owned selection (`[selected]`), NOT the roving focus — and is the source of truth for the
58
+ * selected STYLE too (CSS keys off `:host([aria-selected="true"])`; no `data-skra-*`). On activation it
59
+ * REQUESTS selection from the coordinator (`ctx.select`) — it never mutates selection itself (ADR 037).
60
+ *
61
+ * **Disabled is native-attr-first (boundary #9): the consumer sets the native `disabled` attribute** —
62
+ * Forge OBSERVES it (no `disabled` input). A disabled tab is SKIPPED in the roving order (APG).
63
+ */
64
+ class SkraTab {
65
+ /** Selection key — matched against `skraTabs [selected]` and the owning panel's `value`. */
66
+ value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
67
+ host = inject(ElementRef);
68
+ ctx = inject(SkraTabsContext, { optional: true });
69
+ tabId = computed(() => this.ctx?.tabId(this.value()) ?? null, ...(ngDevMode ? [{ debugName: "tabId" }] : /* istanbul ignore next */ []));
70
+ panelId = computed(() => this.ctx?.panelId(this.value()) ?? null, ...(ngDevMode ? [{ debugName: "panelId" }] : /* istanbul ignore next */ []));
71
+ ariaSelected = computed(() => this.ctx?.isSelected(this.value()) ? 'true' : 'false', ...(ngDevMode ? [{ debugName: "ariaSelected" }] : /* istanbul ignore next */ []));
72
+ tabindex = computed(() => {
73
+ if (!this.ctx)
74
+ return null; // standalone misuse — leave the native button focusable
75
+ return this.ctx.activeValue() === this.value() ? '0' : '-1';
76
+ }, ...(ngDevMode ? [{ debugName: "tabindex" }] : /* istanbul ignore next */ []));
77
+ constructor() {
78
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
79
+ if (!this.ctx) {
80
+ console.warn('[skraTab] has no [skraTabs] ancestor — it is not wired to a tablist (no aria-controls / ' +
81
+ 'roving / selection). Project it inside <div skraTablist> within <element skraTabs>.');
82
+ }
83
+ }
84
+ }
85
+ // ── SkraTabKeyItem (driven by SkraTabsKeyboard / CDK FocusKeyManager) ──
86
+ focus() {
87
+ this.host.nativeElement.focus();
88
+ }
89
+ /** Disabled tabs are skipped in the roving order — read live off the native attribute (no input). */
90
+ isDisabled() {
91
+ return this.host.nativeElement.disabled;
92
+ }
93
+ getValue() {
94
+ return this.value();
95
+ }
96
+ onClick() {
97
+ if (this.host.nativeElement.disabled)
98
+ return; // belt-and-suspenders (native disabled won't fire click)
99
+ this.ctx?.select(this.value());
100
+ }
101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
102
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.7", type: SkraTab, isStandalone: true, selector: "button[skraTab]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "tab", "type": "button" }, listeners: { "click": "onClick()" }, properties: { "attr.id": "tabId()", "attr.aria-controls": "panelId()", "attr.aria-selected": "ariaSelected()", "attr.tabindex": "tabindex()" } }, exportAs: ["skraTab"], ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["@charset \"UTF-8\";:host{display:inline-flex;align-items:center;justify-content:center;gap:var(--skra-comp-tabs-gap);position:relative;margin:0;box-sizing:border-box;appearance:none;cursor:pointer;background:var(--skra-comp-tabs-background);color:var(--skra-comp-tabs-foreground);border-color:var(--skra-comp-tabs-border-color);border-width:var(--skra-comp-tabs-border-width);border-style:var(--skra-comp-tabs-border-style);border-radius:var(--skra-comp-tabs-radius);padding-block:var(--skra-comp-tabs-padding-block);padding-inline:var(--skra-comp-tabs-padding-inline);box-shadow:var(--skra-comp-tabs-shadow);font-family:var(--skra-sys-typography-role-label-large-font-family);font-size:var(--skra-sys-typography-role-label-large-font-size);font-weight:var(--skra-sys-typography-role-label-large-font-weight);line-height:var(--skra-sys-typography-role-label-large-line-height);letter-spacing:var(--skra-sys-typography-role-label-large-letter-spacing);transition:color var(--skra-comp-tabs-motion-duration) var(--skra-comp-tabs-motion-easing),background-color var(--skra-comp-tabs-motion-duration) var(--skra-comp-tabs-motion-easing)}:host:after{content:\"\";position:absolute;inset-inline:0;inset-block-end:calc(-1 * var(--skra-comp-tabs-tablist-border-width));block-size:var(--skra-comp-tabs-indicator-thickness);background-color:var(--skra-comp-tabs-indicator-color);opacity:0;transition:opacity var(--skra-comp-tabs-motion-duration) var(--skra-comp-tabs-motion-easing)}:host(:hover:not(:disabled)){background-color:var(--skra-comp-tabs-background-hover);color:var(--skra-comp-tabs-foreground-selected)}:host(:focus-visible){outline:var(--skra-comp-tabs-focus-ring-width) solid var(--skra-comp-tabs-focus-ring-color);outline-offset:calc(-1 * var(--skra-comp-tabs-focus-ring-width))}:host([aria-selected=true]){color:var(--skra-comp-tabs-foreground-selected)}:host([aria-selected=true]):after{opacity:1}:host(:disabled){color:var(--skra-comp-tabs-foreground-disabled);cursor:default}@media(prefers-reduced-motion:reduce){:host,:host:after{transition-duration:0ms}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
103
+ }
104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTab, decorators: [{
105
+ type: Component,
106
+ args: [{ selector: 'button[skraTab]', standalone: true, exportAs: 'skraTab', template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
107
+ role: 'tab',
108
+ type: 'button', // never an implicit submit if the tablist sits inside a <form>
109
+ '[attr.id]': 'tabId()',
110
+ '[attr.aria-controls]': 'panelId()',
111
+ '[attr.aria-selected]': 'ariaSelected()',
112
+ '[attr.tabindex]': 'tabindex()',
113
+ '(click)': 'onClick()',
114
+ }, styles: ["@charset \"UTF-8\";:host{display:inline-flex;align-items:center;justify-content:center;gap:var(--skra-comp-tabs-gap);position:relative;margin:0;box-sizing:border-box;appearance:none;cursor:pointer;background:var(--skra-comp-tabs-background);color:var(--skra-comp-tabs-foreground);border-color:var(--skra-comp-tabs-border-color);border-width:var(--skra-comp-tabs-border-width);border-style:var(--skra-comp-tabs-border-style);border-radius:var(--skra-comp-tabs-radius);padding-block:var(--skra-comp-tabs-padding-block);padding-inline:var(--skra-comp-tabs-padding-inline);box-shadow:var(--skra-comp-tabs-shadow);font-family:var(--skra-sys-typography-role-label-large-font-family);font-size:var(--skra-sys-typography-role-label-large-font-size);font-weight:var(--skra-sys-typography-role-label-large-font-weight);line-height:var(--skra-sys-typography-role-label-large-line-height);letter-spacing:var(--skra-sys-typography-role-label-large-letter-spacing);transition:color var(--skra-comp-tabs-motion-duration) var(--skra-comp-tabs-motion-easing),background-color var(--skra-comp-tabs-motion-duration) var(--skra-comp-tabs-motion-easing)}:host:after{content:\"\";position:absolute;inset-inline:0;inset-block-end:calc(-1 * var(--skra-comp-tabs-tablist-border-width));block-size:var(--skra-comp-tabs-indicator-thickness);background-color:var(--skra-comp-tabs-indicator-color);opacity:0;transition:opacity var(--skra-comp-tabs-motion-duration) var(--skra-comp-tabs-motion-easing)}:host(:hover:not(:disabled)){background-color:var(--skra-comp-tabs-background-hover);color:var(--skra-comp-tabs-foreground-selected)}:host(:focus-visible){outline:var(--skra-comp-tabs-focus-ring-width) solid var(--skra-comp-tabs-focus-ring-color);outline-offset:calc(-1 * var(--skra-comp-tabs-focus-ring-width))}:host([aria-selected=true]){color:var(--skra-comp-tabs-foreground-selected)}:host([aria-selected=true]):after{opacity:1}:host(:disabled){color:var(--skra-comp-tabs-foreground-disabled);cursor:default}@media(prefers-reduced-motion:reduce){:host,:host:after{transition-duration:0ms}}\n"] }]
115
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }] } });
116
+
117
+ /**
118
+ * SkraTabPanel — the `role="tabpanel"` for one tab, a Component on the consumer's `<div skraTabPanel>`
119
+ * (ADR 040, `template: '<ng-content>'` so the panel body renders). It is focusable (`tabindex="0"`, APG —
120
+ * keyboard users Tab from the active tab into its content), auto-named by `aria-labelledby`→its tab, and
121
+ * `hidden` when its `value` is not the selected one (removed from the a11y tree; stays in the DOM — lazy
122
+ * mounting is the consumer's own `@if` on the content).
123
+ */
124
+ class SkraTabPanel {
125
+ /** Selection key — matched against the owning `button[skraTab]`'s `value`. */
126
+ value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
127
+ ctx = inject(SkraTabsContext, { optional: true });
128
+ panelId = computed(() => this.ctx?.panelId(this.value()) ?? null, ...(ngDevMode ? [{ debugName: "panelId" }] : /* istanbul ignore next */ []));
129
+ tabId = computed(() => this.ctx?.tabId(this.value()) ?? null, ...(ngDevMode ? [{ debugName: "tabId" }] : /* istanbul ignore next */ []));
130
+ /** `''` (attribute present) when not selected → hidden; `null` (absent) when selected → shown. */
131
+ hidden = computed(() => this.ctx?.isSelected(this.value()) ? null : '', ...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
132
+ constructor() {
133
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
134
+ if (!this.ctx) {
135
+ console.warn('[skraTabPanel] has no [skraTabs] ancestor — it cannot be linked to a tab or shown/hidden. ' +
136
+ 'Project it inside <element skraTabs>.');
137
+ }
138
+ }
139
+ }
140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
141
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.7", type: SkraTabPanel, isStandalone: true, selector: "[skraTabPanel]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "tabpanel", "tabindex": "0" }, properties: { "attr.id": "panelId()", "attr.aria-labelledby": "tabId()", "attr.hidden": "hidden()" } }, exportAs: ["skraTabPanel"], ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["@charset \"UTF-8\";:host{display:block;margin:0;box-sizing:border-box;padding-block:var(--skra-comp-tabs-panel-padding-block);padding-inline:var(--skra-comp-tabs-panel-padding-inline)}:host([hidden]){display:none}:host(:focus-visible){outline:var(--skra-comp-tabs-focus-ring-width) solid var(--skra-comp-tabs-focus-ring-color);outline-offset:calc(-1 * var(--skra-comp-tabs-focus-ring-width))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
142
+ }
143
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabPanel, decorators: [{
144
+ type: Component,
145
+ args: [{ selector: '[skraTabPanel]', standalone: true, exportAs: 'skraTabPanel', template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
146
+ role: 'tabpanel',
147
+ tabindex: '0',
148
+ '[attr.id]': 'panelId()',
149
+ '[attr.aria-labelledby]': 'tabId()',
150
+ '[attr.hidden]': 'hidden()',
151
+ }, styles: ["@charset \"UTF-8\";:host{display:block;margin:0;box-sizing:border-box;padding-block:var(--skra-comp-tabs-panel-padding-block);padding-inline:var(--skra-comp-tabs-panel-padding-inline)}:host([hidden]){display:none}:host(:focus-visible){outline:var(--skra-comp-tabs-focus-ring-width) solid var(--skra-comp-tabs-focus-ring-color);outline-offset:calc(-1 * var(--skra-comp-tabs-focus-ring-width))}\n"] }]
152
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }] } });
153
+
154
+ /**
155
+ * SkraTabsKeyboard — the WAI-ARIA APG **Tabs** roving-tabindex keyboard model, confined to ONE file.
156
+ *
157
+ * This is the **only** module in `@skra/navigation` that imports `@angular/cdk` (ADR 0044 §7,
158
+ * generalized library-wide 2026-06-19). It wraps CDK `FocusKeyManager` (roving REAL focus) so the
159
+ * engine is swappable in one file — exactly as `SkraMenuKeyboard` does for the menu. Unlike the menu,
160
+ * tabs use **horizontal** orientation (Left/Right) and **skip** disabled tabs.
161
+ *
162
+ * Owns: Left/Right roving (wrap), Home/End. Enter/Space (native button activation) and selection are
163
+ * the coordinator's job — the controller only moves focus and reports the active tab's value.
164
+ */
165
+ class SkraTabsKeyboard {
166
+ manager;
167
+ items = [];
168
+ /**
169
+ * (Re)build the manager from the current projected tab set. Called from an `effect` on the signal
170
+ * `contentChildren` — a passed array is a static snapshot, so the manager is rebuilt whenever the
171
+ * tab set changes (the documented signal↔CDK seam, mirroring `menu-keyboard.ts`).
172
+ */
173
+ setItems(items) {
174
+ this.items = [...items];
175
+ this.manager?.destroy();
176
+ this.manager = new FocusKeyManager(this.items)
177
+ .withHorizontalOrientation('ltr') // Left/Right; RTL handled by the manager via the page dir
178
+ .withWrap()
179
+ .withHomeAndEnd()
180
+ // Tabs SKIP disabled (APG): a disabled tab is not a roving stop. (Menu keeps them focusable.)
181
+ .skipPredicate((tab) => tab.isDisabled());
182
+ }
183
+ /** Delegate Left/Right/Home/End. Enter/Space/Tab are intentionally left to the platform/coordinator. */
184
+ onKeydown(event) {
185
+ this.manager?.onKeydown(event);
186
+ }
187
+ /** The value of the tab the manager currently considers active (focused after `onKeydown`). */
188
+ activeValue() {
189
+ return this.manager?.activeItem?.getValue();
190
+ }
191
+ /** Sync the manager's active index to a value WITHOUT moving focus (after a click / on init). */
192
+ setActive(value) {
193
+ const index = this.items.findIndex((tab) => tab.getValue() === value);
194
+ if (index >= 0)
195
+ this.manager?.updateActiveItem(index);
196
+ }
197
+ /** The first non-disabled tab's value (the roving stop when nothing is selected) — APG. */
198
+ firstEnabledValue() {
199
+ return this.items.find((tab) => !tab.isDisabled())?.getValue();
200
+ }
201
+ hasValue(value) {
202
+ return this.items.some((tab) => tab.getValue() === value);
203
+ }
204
+ destroy() {
205
+ this.manager?.destroy();
206
+ this.manager = undefined;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * SkraTabs — the WAI-ARIA APG **Tabs** coordinator. A directive on a wrapping element that holds a
212
+ * `[skraTablist]` (the `role="tablist"` with its `button[skraTab]` children) and the
213
+ * `[skraTabPanel]` panels as DI descendants. It provides `SkraTabsContext` (the ADR 013 §3.6 non-form
214
+ * Contextual-ID-Sync bus), owns the consumer-facing `[selected]` / `(selectedChange)` (controlled,
215
+ * ADR 037 — NO internal selection state) and `[activation]`, and drives the roving-tabindex keyboard
216
+ * (`SkraTabsKeyboard` → CDK `FocusKeyManager`).
217
+ *
218
+ * <div skraTabs [selected]="tab()" (selectedChange)="tab.set($event)">
219
+ * <div skraTablist aria-label="Account">
220
+ * <button skraTab value="profile">Profile</button>
221
+ * <button skraTab value="security">Security</button>
222
+ * </div>
223
+ * <div skraTabPanel value="profile">…</div>
224
+ * <div skraTabPanel value="security">…</div>
225
+ * </div>
226
+ *
227
+ * Selection is consumer-owned: a click / Enter / Space / (automatic) arrow emits `(selectedChange)`;
228
+ * the visible selection updates when the consumer round-trips `[selected]`. The roving focus position
229
+ * (`tabindex="0"`) is component-owned UI state — it moves immediately, independent of `[selected]`.
230
+ *
231
+ * Full contract: adr/specs/component-tabs-spec.md.
232
+ */
233
+ class SkraTabs {
234
+ /** Consumer-owned selection key (matches a tab's `value`); '' / no-match → nothing selected (ADR 037). */
235
+ selected = input('', ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
236
+ /** Automatic (selection follows roving focus) | manual (Enter/Space selects). */
237
+ activation = input('automatic', ...(ngDevMode ? [{ debugName: "activation" }] : /* istanbul ignore next */ []));
238
+ /** Fires ONLY on user activation — never on a programmatic `[selected]` change. */
239
+ selectedChange = output();
240
+ ctx = inject(SkraTabsContext);
241
+ tabs = contentChildren(SkraTab, { ...(ngDevMode ? { debugName: "tabs" } : /* istanbul ignore next */ {}), descendants: true });
242
+ panels = contentChildren(SkraTabPanel, { ...(ngDevMode ? { debugName: "panels" } : /* istanbul ignore next */ {}), descendants: true });
243
+ keyboard = new SkraTabsKeyboard();
244
+ constructor() {
245
+ // Wire the context bus (a tab requests selection; the tablist forwards keydown).
246
+ this.ctx.select = (value) => this.selectTab(value);
247
+ this.ctx.handleKeydown = (event) => this.onTablistKeydown(event);
248
+ // Mirror inputs → context (read-only to children).
249
+ effect(() => this.ctx.selected.set(this.selected()));
250
+ effect(() => this.ctx.activation.set(this.activation()));
251
+ // (Re)build the keyboard from the projected tab set and (re)seat the roving stop whenever the tab
252
+ // set or the selection changes. Rebuilding the manager for a handful of tabs is trivial.
253
+ effect(() => {
254
+ this.keyboard.setItems(this.tabs());
255
+ this.reseat();
256
+ });
257
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
258
+ effect(() => this.warnMismatches());
259
+ }
260
+ inject(DestroyRef).onDestroy(() => this.keyboard.destroy());
261
+ }
262
+ /** Seat the roving stop (`tabindex="0"`) on a real, enabled tab: the selected one if valid, else the
263
+ * first enabled tab (APG — the tablist must always have exactly one tab stop). */
264
+ reseat() {
265
+ const sel = this.selected();
266
+ const next = this.keyboard.hasValue(sel)
267
+ ? sel
268
+ : (this.keyboard.firstEnabledValue() ?? '');
269
+ this.ctx.activeValue.set(next);
270
+ if (next)
271
+ this.keyboard.setActive(next);
272
+ }
273
+ /** A tab was activated (click / Enter / Space / automatic arrow). Move the roving stop immediately
274
+ * (UI state), then REQUEST selection from the consumer — never mutate selection internally (ADR 037). */
275
+ selectTab(value) {
276
+ this.ctx.activeValue.set(value);
277
+ this.keyboard.setActive(value);
278
+ if (this.selected() === value)
279
+ return; // idempotent — no redundant output
280
+ this.selectedChange.emit(value);
281
+ }
282
+ onTablistKeydown(event) {
283
+ const before = this.keyboard.activeValue();
284
+ this.keyboard.onKeydown(event);
285
+ const after = this.keyboard.activeValue();
286
+ if (after && after !== before) {
287
+ this.ctx.activeValue.set(after); // roving moved (focus already on the new tab)
288
+ if (this.ctx.activation() === 'automatic')
289
+ this.selectTab(after);
290
+ }
291
+ }
292
+ /** Dev-only nudge: every tab needs a panel of the same `value`, and vice-versa (aria-controls / -labelledby). */
293
+ warnMismatches() {
294
+ const tabValues = new Set(this.tabs().map((t) => t.value()));
295
+ const panelValues = new Set(this.panels().map((p) => p.value()));
296
+ for (const v of tabValues) {
297
+ if (!panelValues.has(v))
298
+ console.warn(`[skraTabs] tab value="${v}" has no matching [skraTabPanel] — its aria-controls points to a missing panel.`);
299
+ }
300
+ for (const v of panelValues) {
301
+ if (!tabValues.has(v))
302
+ console.warn(`[skraTabs] panel value="${v}" has no matching button[skraTab] — it will never be revealed.`);
303
+ }
304
+ }
305
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
306
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.7", type: SkraTabs, isStandalone: true, selector: "[skraTabs]", inputs: { selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, activation: { classPropertyName: "activation", publicName: "activation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectedChange: "selectedChange" }, providers: [SkraTabsContext], queries: [{ propertyName: "tabs", predicate: SkraTab, descendants: true, isSignal: true }, { propertyName: "panels", predicate: SkraTabPanel, descendants: true, isSignal: true }], exportAs: ["skraTabs"], ngImport: i0 });
307
+ }
308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTabs, decorators: [{
309
+ type: Directive,
310
+ args: [{
311
+ selector: '[skraTabs]',
312
+ standalone: true,
313
+ exportAs: 'skraTabs',
314
+ providers: [SkraTabsContext],
315
+ }]
316
+ }], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], activation: [{ type: i0.Input, args: [{ isSignal: true, alias: "activation", required: false }] }], selectedChange: [{ type: i0.Output, args: ["selectedChange"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SkraTab), { ...{ descendants: true }, isSignal: true }] }], panels: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SkraTabPanel), { ...{
317
+ descendants: true,
318
+ }, isSignal: true }] }] } });
319
+
320
+ /**
321
+ * SkraTablist — the `role="tablist"` container, a Component on the consumer's `<div skraTablist>`
322
+ * (ADR 040, `template: '<ng-content>'` so the projected `button[skraTab]` children render). It carries
323
+ * the rail styling and forwards its `keydown` to the coordinator's roving keyboard (`SkraTabsKeyboard`)
324
+ * via the `SkraTabsContext` bus. Name it for AT with consumer `aria-label`/`aria-labelledby` (ADR 033).
325
+ */
326
+ class SkraTablist {
327
+ ctx = inject(SkraTabsContext, { optional: true });
328
+ constructor() {
329
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
330
+ if (!this.ctx) {
331
+ console.warn('[skraTablist] has no [skraTabs] ancestor — its tabs cannot be keyboard-navigated. ' +
332
+ 'Wrap the tablist + panels in <element skraTabs>.');
333
+ }
334
+ }
335
+ }
336
+ onKeydown(event) {
337
+ this.ctx?.handleKeydown(event);
338
+ }
339
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTablist, deps: [], target: i0.ɵɵFactoryTarget.Component });
340
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: SkraTablist, isStandalone: true, selector: "[skraTablist]", host: { attributes: { "role": "tablist", "aria-orientation": "horizontal" }, listeners: { "keydown": "onKeydown($event)" } }, exportAs: ["skraTablist"], ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: ["@charset \"UTF-8\";:host{display:flex;align-items:stretch;gap:var(--skra-comp-tabs-gap);margin:0;box-sizing:border-box;border-block-end:var(--skra-comp-tabs-tablist-border-width) solid var(--skra-comp-tabs-tablist-border-color)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
341
+ }
342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTablist, decorators: [{
343
+ type: Component,
344
+ args: [{ selector: '[skraTablist]', standalone: true, exportAs: 'skraTablist', template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
345
+ role: 'tablist',
346
+ 'aria-orientation': 'horizontal',
347
+ '(keydown)': 'onKeydown($event)',
348
+ }, styles: ["@charset \"UTF-8\";:host{display:flex;align-items:stretch;gap:var(--skra-comp-tabs-gap);margin:0;box-sizing:border-box;border-block-end:var(--skra-comp-tabs-tablist-border-width) solid var(--skra-comp-tabs-tablist-border-color)}\n"] }]
349
+ }], ctorParameters: () => [] });
350
+
351
+ /**
352
+ * Generated bundle index. Do not edit.
353
+ */
354
+
355
+ export { SkraTab, SkraTabPanel, SkraTablist, SkraTabs, SkraTabsContext, SkraTabsKeyboard };
356
+ //# sourceMappingURL=skra-ui-tabs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skra-ui-tabs.mjs","sources":["../../../../libs/ui/tabs/src/lib/tabs/tabs-context.ts","../../../../libs/ui/tabs/src/lib/tabs/tab.ts","../../../../libs/ui/tabs/src/lib/tabs/tab-panel.ts","../../../../libs/ui/tabs/src/lib/tabs/tabs-keyboard.ts","../../../../libs/ui/tabs/src/lib/tabs/tabs.ts","../../../../libs/ui/tabs/src/lib/tabs/tablist.ts","../../../../libs/ui/tabs/src/skra-ui-tabs.ts"],"sourcesContent":["import { Injectable, signal } from '@angular/core';\n\n/** Module-global counter → a deterministic, unique id base per `[skraTabs]` instance.\n * Equivalent to the forms `IdGeneratorService` (ADR 013) but lib-local, so `@skra/navigation`\n * takes no dependency on `@skra/forms` just for id generation. Stable across SSR↔hydration\n * (same instantiation order yields the same ids — the standard counter technique). */\nlet tabsInstanceCounter = 0;\n\n/** Sanitize a consumer `value` into an id-safe fragment (no spaces / special chars). */\nfunction idSafe(value: string): string {\n return value.replace(/[^a-zA-Z0-9_-]/g, '-');\n}\n\n/**\n * DI Context provided per `[skraTabs]` instance — the ADR 013 §3.6 **non-form** Contextual-ID-Sync\n * bus wiring `tab` ↔ `tabpanel`. It carries **IDs + derived, read-only interaction state only**\n * (`selected` mirrors the consumer's `[selected]`; `activeValue` is the roving-focus position). It\n * **never owns value** — selection stays consumer-owned (ADR 037); a tab requests a change through\n * `select()`, which the coordinator implements and from which it emits `(selectedChange)`.\n */\n@Injectable()\nexport class SkraTabsContext {\n /** Unique per `[skraTabs]` — the prefix for every tab/panel id in this group. */\n private readonly baseId = `skra-tabs-${++tabsInstanceCounter}`;\n\n /** Mirror of the consumer's `[selected]` — drives `aria-selected` + panel visibility (read-only to children). */\n readonly selected = signal<string>('');\n /** Roving-focus position — the value of the tab that currently carries `tabindex=\"0\"` (read-only to children). */\n readonly activeValue = signal<string>('');\n /** Activation model (read-only to children). */\n readonly activation = signal<'automatic' | 'manual'>('automatic');\n\n tabId(value: string): string {\n return `${this.baseId}-tab-${idSafe(value)}`;\n }\n panelId(value: string): string {\n return `${this.baseId}-panel-${idSafe(value)}`;\n }\n isSelected(value: string): boolean {\n return this.selected() === value;\n }\n\n /** Coordinator-assigned: a tab/keyboard requests selection (the coordinator emits `(selectedChange)`). */\n select: (value: string) => void = () => undefined;\n /** Coordinator-assigned: the tablist forwards its `keydown` here (routed to the keyboard controller). */\n handleKeydown: (event: KeyboardEvent) => void = () => undefined;\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n} from '@angular/core';\n\nimport { SkraTabsContext } from './tabs-context';\nimport type { SkraTabKeyItem } from './tabs-types';\n\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * SkraTab — a single tab, a Component on the native command host `button[skraTab]` (ADR 012/040:\n * a `<button>` gives Enter/Space→click + focusability for free; its implicit role is overridden to\n * `role=\"tab\"`, the precedent set by `button[skraMenuItem]`).\n *\n * Roving tabindex: `tabindex=\"0\"` only when this tab is the roving stop (`SkraTabsContext.activeValue`),\n * else `-1` — the controller (`SkraTabsKeyboard`) moves real focus. `aria-selected` reflects the\n * consumer-owned selection (`[selected]`), NOT the roving focus — and is the source of truth for the\n * selected STYLE too (CSS keys off `:host([aria-selected=\"true\"])`; no `data-skra-*`). On activation it\n * REQUESTS selection from the coordinator (`ctx.select`) — it never mutates selection itself (ADR 037).\n *\n * **Disabled is native-attr-first (boundary #9): the consumer sets the native `disabled` attribute** —\n * Forge OBSERVES it (no `disabled` input). A disabled tab is SKIPPED in the roving order (APG).\n */\n@Component({\n selector: 'button[skraTab]',\n standalone: true,\n exportAs: 'skraTab',\n template: '<ng-content></ng-content>',\n styleUrl: './tab.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n role: 'tab',\n type: 'button', // never an implicit submit if the tablist sits inside a <form>\n '[attr.id]': 'tabId()',\n '[attr.aria-controls]': 'panelId()',\n '[attr.aria-selected]': 'ariaSelected()',\n '[attr.tabindex]': 'tabindex()',\n '(click)': 'onClick()',\n },\n})\nexport class SkraTab implements SkraTabKeyItem {\n /** Selection key — matched against `skraTabs [selected]` and the owning panel's `value`. */\n readonly value = input.required<string>();\n\n private readonly host = inject<ElementRef<HTMLButtonElement>>(ElementRef);\n private readonly ctx = inject(SkraTabsContext, { optional: true });\n\n protected readonly tabId = computed(\n () => this.ctx?.tabId(this.value()) ?? null,\n );\n protected readonly panelId = computed(\n () => this.ctx?.panelId(this.value()) ?? null,\n );\n protected readonly ariaSelected = computed(() =>\n this.ctx?.isSelected(this.value()) ? 'true' : 'false',\n );\n protected readonly tabindex = computed(() => {\n if (!this.ctx) return null; // standalone misuse — leave the native button focusable\n return this.ctx.activeValue() === this.value() ? '0' : '-1';\n });\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this.ctx) {\n console.warn(\n '[skraTab] has no [skraTabs] ancestor — it is not wired to a tablist (no aria-controls / ' +\n 'roving / selection). Project it inside <div skraTablist> within <element skraTabs>.',\n );\n }\n }\n }\n\n // ── SkraTabKeyItem (driven by SkraTabsKeyboard / CDK FocusKeyManager) ──\n focus(): void {\n this.host.nativeElement.focus();\n }\n /** Disabled tabs are skipped in the roving order — read live off the native attribute (no input). */\n isDisabled(): boolean {\n return this.host.nativeElement.disabled;\n }\n getValue(): string {\n return this.value();\n }\n\n protected onClick(): void {\n if (this.host.nativeElement.disabled) return; // belt-and-suspenders (native disabled won't fire click)\n this.ctx?.select(this.value());\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n computed,\n inject,\n input,\n} from '@angular/core';\n\nimport { SkraTabsContext } from './tabs-context';\n\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * SkraTabPanel — the `role=\"tabpanel\"` for one tab, a Component on the consumer's `<div skraTabPanel>`\n * (ADR 040, `template: '<ng-content>'` so the panel body renders). It is focusable (`tabindex=\"0\"`, APG —\n * keyboard users Tab from the active tab into its content), auto-named by `aria-labelledby`→its tab, and\n * `hidden` when its `value` is not the selected one (removed from the a11y tree; stays in the DOM — lazy\n * mounting is the consumer's own `@if` on the content).\n */\n@Component({\n selector: '[skraTabPanel]',\n standalone: true,\n exportAs: 'skraTabPanel',\n template: '<ng-content></ng-content>',\n styleUrl: './tab-panel.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n role: 'tabpanel',\n tabindex: '0',\n '[attr.id]': 'panelId()',\n '[attr.aria-labelledby]': 'tabId()',\n '[attr.hidden]': 'hidden()',\n },\n})\nexport class SkraTabPanel {\n /** Selection key — matched against the owning `button[skraTab]`'s `value`. */\n readonly value = input.required<string>();\n\n private readonly ctx = inject(SkraTabsContext, { optional: true });\n\n protected readonly panelId = computed(\n () => this.ctx?.panelId(this.value()) ?? null,\n );\n protected readonly tabId = computed(\n () => this.ctx?.tabId(this.value()) ?? null,\n );\n /** `''` (attribute present) when not selected → hidden; `null` (absent) when selected → shown. */\n protected readonly hidden = computed(() =>\n this.ctx?.isSelected(this.value()) ? null : '',\n );\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this.ctx) {\n console.warn(\n '[skraTabPanel] has no [skraTabs] ancestor — it cannot be linked to a tab or shown/hidden. ' +\n 'Project it inside <element skraTabs>.',\n );\n }\n }\n }\n}\n","import { FocusKeyManager } from '@angular/cdk/a11y';\n\nimport type { SkraTabKeyItem } from './tabs-types';\n\n/**\n * SkraTabsKeyboard — the WAI-ARIA APG **Tabs** roving-tabindex keyboard model, confined to ONE file.\n *\n * This is the **only** module in `@skra/navigation` that imports `@angular/cdk` (ADR 0044 §7,\n * generalized library-wide 2026-06-19). It wraps CDK `FocusKeyManager` (roving REAL focus) so the\n * engine is swappable in one file — exactly as `SkraMenuKeyboard` does for the menu. Unlike the menu,\n * tabs use **horizontal** orientation (Left/Right) and **skip** disabled tabs.\n *\n * Owns: Left/Right roving (wrap), Home/End. Enter/Space (native button activation) and selection are\n * the coordinator's job — the controller only moves focus and reports the active tab's value.\n */\nexport class SkraTabsKeyboard {\n private manager?: FocusKeyManager<SkraTabKeyItem>;\n private items: SkraTabKeyItem[] = [];\n\n /**\n * (Re)build the manager from the current projected tab set. Called from an `effect` on the signal\n * `contentChildren` — a passed array is a static snapshot, so the manager is rebuilt whenever the\n * tab set changes (the documented signal↔CDK seam, mirroring `menu-keyboard.ts`).\n */\n setItems(items: readonly SkraTabKeyItem[]): void {\n this.items = [...items];\n this.manager?.destroy();\n this.manager = new FocusKeyManager<SkraTabKeyItem>(this.items)\n .withHorizontalOrientation('ltr') // Left/Right; RTL handled by the manager via the page dir\n .withWrap()\n .withHomeAndEnd()\n // Tabs SKIP disabled (APG): a disabled tab is not a roving stop. (Menu keeps them focusable.)\n .skipPredicate((tab) => tab.isDisabled());\n }\n\n /** Delegate Left/Right/Home/End. Enter/Space/Tab are intentionally left to the platform/coordinator. */\n onKeydown(event: KeyboardEvent): void {\n this.manager?.onKeydown(event);\n }\n\n /** The value of the tab the manager currently considers active (focused after `onKeydown`). */\n activeValue(): string | undefined {\n return this.manager?.activeItem?.getValue();\n }\n\n /** Sync the manager's active index to a value WITHOUT moving focus (after a click / on init). */\n setActive(value: string): void {\n const index = this.items.findIndex((tab) => tab.getValue() === value);\n if (index >= 0) this.manager?.updateActiveItem(index);\n }\n\n /** The first non-disabled tab's value (the roving stop when nothing is selected) — APG. */\n firstEnabledValue(): string | undefined {\n return this.items.find((tab) => !tab.isDisabled())?.getValue();\n }\n\n hasValue(value: string): boolean {\n return this.items.some((tab) => tab.getValue() === value);\n }\n\n destroy(): void {\n this.manager?.destroy();\n this.manager = undefined;\n }\n}\n","import {\n DestroyRef,\n Directive,\n contentChildren,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\nimport { SkraTab } from './tab';\nimport { SkraTabPanel } from './tab-panel';\nimport { SkraTabsContext } from './tabs-context';\nimport { SkraTabsKeyboard } from './tabs-keyboard';\n\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * SkraTabs — the WAI-ARIA APG **Tabs** coordinator. A directive on a wrapping element that holds a\n * `[skraTablist]` (the `role=\"tablist\"` with its `button[skraTab]` children) and the\n * `[skraTabPanel]` panels as DI descendants. It provides `SkraTabsContext` (the ADR 013 §3.6 non-form\n * Contextual-ID-Sync bus), owns the consumer-facing `[selected]` / `(selectedChange)` (controlled,\n * ADR 037 — NO internal selection state) and `[activation]`, and drives the roving-tabindex keyboard\n * (`SkraTabsKeyboard` → CDK `FocusKeyManager`).\n *\n * <div skraTabs [selected]=\"tab()\" (selectedChange)=\"tab.set($event)\">\n * <div skraTablist aria-label=\"Account\">\n * <button skraTab value=\"profile\">Profile</button>\n * <button skraTab value=\"security\">Security</button>\n * </div>\n * <div skraTabPanel value=\"profile\">…</div>\n * <div skraTabPanel value=\"security\">…</div>\n * </div>\n *\n * Selection is consumer-owned: a click / Enter / Space / (automatic) arrow emits `(selectedChange)`;\n * the visible selection updates when the consumer round-trips `[selected]`. The roving focus position\n * (`tabindex=\"0\"`) is component-owned UI state — it moves immediately, independent of `[selected]`.\n *\n * Full contract: adr/specs/component-tabs-spec.md.\n */\n@Directive({\n selector: '[skraTabs]',\n standalone: true,\n exportAs: 'skraTabs',\n providers: [SkraTabsContext],\n})\nexport class SkraTabs {\n /** Consumer-owned selection key (matches a tab's `value`); '' / no-match → nothing selected (ADR 037). */\n readonly selected = input<string>('');\n /** Automatic (selection follows roving focus) | manual (Enter/Space selects). */\n readonly activation = input<'automatic' | 'manual'>('automatic');\n /** Fires ONLY on user activation — never on a programmatic `[selected]` change. */\n readonly selectedChange = output<string>();\n\n private readonly ctx = inject(SkraTabsContext);\n private readonly tabs = contentChildren(SkraTab, { descendants: true });\n private readonly panels = contentChildren(SkraTabPanel, {\n descendants: true,\n });\n private readonly keyboard = new SkraTabsKeyboard();\n\n constructor() {\n // Wire the context bus (a tab requests selection; the tablist forwards keydown).\n this.ctx.select = (value) => this.selectTab(value);\n this.ctx.handleKeydown = (event) => this.onTablistKeydown(event);\n\n // Mirror inputs → context (read-only to children).\n effect(() => this.ctx.selected.set(this.selected()));\n effect(() => this.ctx.activation.set(this.activation()));\n\n // (Re)build the keyboard from the projected tab set and (re)seat the roving stop whenever the tab\n // set or the selection changes. Rebuilding the manager for a handful of tabs is trivial.\n effect(() => {\n this.keyboard.setItems(this.tabs());\n this.reseat();\n });\n\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n effect(() => this.warnMismatches());\n }\n\n inject(DestroyRef).onDestroy(() => this.keyboard.destroy());\n }\n\n /** Seat the roving stop (`tabindex=\"0\"`) on a real, enabled tab: the selected one if valid, else the\n * first enabled tab (APG — the tablist must always have exactly one tab stop). */\n private reseat(): void {\n const sel = this.selected();\n const next = this.keyboard.hasValue(sel)\n ? sel\n : (this.keyboard.firstEnabledValue() ?? '');\n this.ctx.activeValue.set(next);\n if (next) this.keyboard.setActive(next);\n }\n\n /** A tab was activated (click / Enter / Space / automatic arrow). Move the roving stop immediately\n * (UI state), then REQUEST selection from the consumer — never mutate selection internally (ADR 037). */\n private selectTab(value: string): void {\n this.ctx.activeValue.set(value);\n this.keyboard.setActive(value);\n if (this.selected() === value) return; // idempotent — no redundant output\n this.selectedChange.emit(value);\n }\n\n private onTablistKeydown(event: KeyboardEvent): void {\n const before = this.keyboard.activeValue();\n this.keyboard.onKeydown(event);\n const after = this.keyboard.activeValue();\n if (after && after !== before) {\n this.ctx.activeValue.set(after); // roving moved (focus already on the new tab)\n if (this.ctx.activation() === 'automatic') this.selectTab(after);\n }\n }\n\n /** Dev-only nudge: every tab needs a panel of the same `value`, and vice-versa (aria-controls / -labelledby). */\n private warnMismatches(): void {\n const tabValues = new Set(this.tabs().map((t) => t.value()));\n const panelValues = new Set(this.panels().map((p) => p.value()));\n for (const v of tabValues) {\n if (!panelValues.has(v))\n console.warn(\n `[skraTabs] tab value=\"${v}\" has no matching [skraTabPanel] — its aria-controls points to a missing panel.`,\n );\n }\n for (const v of panelValues) {\n if (!tabValues.has(v))\n console.warn(\n `[skraTabs] panel value=\"${v}\" has no matching button[skraTab] — it will never be revealed.`,\n );\n }\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n inject,\n} from '@angular/core';\n\nimport { SkraTabsContext } from './tabs-context';\n\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * SkraTablist — the `role=\"tablist\"` container, a Component on the consumer's `<div skraTablist>`\n * (ADR 040, `template: '<ng-content>'` so the projected `button[skraTab]` children render). It carries\n * the rail styling and forwards its `keydown` to the coordinator's roving keyboard (`SkraTabsKeyboard`)\n * via the `SkraTabsContext` bus. Name it for AT with consumer `aria-label`/`aria-labelledby` (ADR 033).\n */\n@Component({\n selector: '[skraTablist]',\n standalone: true,\n exportAs: 'skraTablist',\n template: '<ng-content></ng-content>',\n styleUrl: './tablist.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n role: 'tablist',\n 'aria-orientation': 'horizontal',\n '(keydown)': 'onKeydown($event)',\n },\n})\nexport class SkraTablist {\n private readonly ctx = inject(SkraTabsContext, { optional: true });\n\n constructor() {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this.ctx) {\n console.warn(\n '[skraTablist] has no [skraTabs] ancestor — its tabs cannot be keyboard-navigated. ' +\n 'Wrap the tablist + panels in <element skraTabs>.',\n );\n }\n }\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n this.ctx?.handleKeydown(event);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAEA;;;AAGsF;AACtF,IAAI,mBAAmB,GAAG,CAAC;AAE3B;AACA,SAAS,MAAM,CAAC,KAAa,EAAA;IAC3B,OAAO,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;AAC9C;AAEA;;;;;;AAMG;MAEU,eAAe,CAAA;;AAET,IAAA,MAAM,GAAG,CAAA,UAAA,EAAa,EAAE,mBAAmB,EAAE;;AAGrD,IAAA,QAAQ,GAAG,MAAM,CAAS,EAAE,+EAAC;;AAE7B,IAAA,WAAW,GAAG,MAAM,CAAS,EAAE,kFAAC;;AAEhC,IAAA,UAAU,GAAG,MAAM,CAAyB,WAAW,iFAAC;AAEjE,IAAA,KAAK,CAAC,KAAa,EAAA;QACjB,OAAO,CAAA,EAAG,IAAI,CAAC,MAAM,CAAA,KAAA,EAAQ,MAAM,CAAC,KAAK,CAAC,CAAA,CAAE;IAC9C;AACA,IAAA,OAAO,CAAC,KAAa,EAAA;QACnB,OAAO,CAAA,EAAG,IAAI,CAAC,MAAM,CAAA,OAAA,EAAU,MAAM,CAAC,KAAK,CAAC,CAAA,CAAE;IAChD;AACA,IAAA,UAAU,CAAC,KAAa,EAAA;AACtB,QAAA,OAAO,IAAI,CAAC,QAAQ,EAAE,KAAK,KAAK;IAClC;;AAGA,IAAA,MAAM,GAA4B,MAAM,SAAS;;AAEjD,IAAA,aAAa,GAAmC,MAAM,SAAS;uGAxBpD,eAAe,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;2GAAf,eAAe,EAAA,CAAA;;2FAAf,eAAe,EAAA,UAAA,EAAA,CAAA;kBAD3B;;;ACLD;;;;;;;;;;;;;AAaG;MAmBU,OAAO,CAAA;;AAET,IAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,2EAAU;AAExB,IAAA,IAAI,GAAG,MAAM,CAAgC,UAAU,CAAC;IACxD,GAAG,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAE/C,KAAK,GAAG,QAAQ,CACjC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,4EAC5C;IACkB,OAAO,GAAG,QAAQ,CACnC,MAAM,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,8EAC9C;IACkB,YAAY,GAAG,QAAQ,CAAC,MACzC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,MAAM,GAAG,OAAO,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,cAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CACtD;AACkB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAK;QAC1C,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;AAC3B,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,IAAI;AAC7D,IAAA,CAAC,+EAAC;AAEF,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,EAAE;AACjD,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;gBACb,OAAO,CAAC,IAAI,CACV,0FAA0F;AACxF,oBAAA,qFAAqF,CACxF;YACH;QACF;IACF;;IAGA,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE;IACjC;;IAEA,UAAU,GAAA;AACR,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ;IACzC;IACA,QAAQ,GAAA;AACN,QAAA,OAAO,IAAI,CAAC,KAAK,EAAE;IACrB;IAEU,OAAO,GAAA;AACf,QAAA,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ;AAAE,YAAA,OAAO;QAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IAChC;uGA/CW,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAP,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,OAAO,qdAdR,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,qhEAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAc1B,OAAO,EAAA,UAAA,EAAA,CAAA;kBAlBnB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,EAAA,UAAA,EACf,IAAI,EAAA,QAAA,EACN,SAAS,YACT,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;AACJ,wBAAA,IAAI,EAAE,KAAK;wBACX,IAAI,EAAE,QAAQ;AACd,wBAAA,WAAW,EAAE,SAAS;AACtB,wBAAA,sBAAsB,EAAE,WAAW;AACnC,wBAAA,sBAAsB,EAAE,gBAAgB;AACxC,wBAAA,iBAAiB,EAAE,YAAY;AAC/B,wBAAA,SAAS,EAAE,WAAW;AACvB,qBAAA,EAAA,MAAA,EAAA,CAAA,qhEAAA,CAAA,EAAA;;;AChCH;;;;;;AAMG;MAiBU,YAAY,CAAA;;AAEd,IAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,2EAAU;IAExB,GAAG,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAE/C,OAAO,GAAG,QAAQ,CACnC,MAAM,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,8EAC9C;IACkB,KAAK,GAAG,QAAQ,CACjC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,4EAC5C;;IAEkB,MAAM,GAAG,QAAQ,CAAC,MACnC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,EAAE,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,QAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAC/C;AAED,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,EAAE;AACjD,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;gBACb,OAAO,CAAC,IAAI,CACV,4FAA4F;AAC1F,oBAAA,uCAAuC,CAC1C;YACH;QACF;IACF;uGA1BW,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,8YAZb,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,4YAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAY1B,YAAY,EAAA,UAAA,EAAA,CAAA;kBAhBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gBAAgB,EAAA,UAAA,EACd,IAAI,EAAA,QAAA,EACN,cAAc,YACd,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;AACJ,wBAAA,IAAI,EAAE,UAAU;AAChB,wBAAA,QAAQ,EAAE,GAAG;AACb,wBAAA,WAAW,EAAE,WAAW;AACxB,wBAAA,wBAAwB,EAAE,SAAS;AACnC,wBAAA,eAAe,EAAE,UAAU;AAC5B,qBAAA,EAAA,MAAA,EAAA,CAAA,4YAAA,CAAA,EAAA;;;AC9BH;;;;;;;;;;AAUG;MACU,gBAAgB,CAAA;AACnB,IAAA,OAAO;IACP,KAAK,GAAqB,EAAE;AAEpC;;;;AAIG;AACH,IAAA,QAAQ,CAAC,KAAgC,EAAA;AACvC,QAAA,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC;AACvB,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE;QACvB,IAAI,CAAC,OAAO,GAAG,IAAI,eAAe,CAAiB,IAAI,CAAC,KAAK;AAC1D,aAAA,yBAAyB,CAAC,KAAK,CAAC;AAChC,aAAA,QAAQ;AACR,aAAA,cAAc;;aAEd,aAAa,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;IAC7C;;AAGA,IAAA,SAAS,CAAC,KAAoB,EAAA;AAC5B,QAAA,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC;IAChC;;IAGA,WAAW,GAAA;QACT,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE;IAC7C;;AAGA,IAAA,SAAS,CAAC,KAAa,EAAA;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,QAAQ,EAAE,KAAK,KAAK,CAAC;QACrE,IAAI,KAAK,IAAI,CAAC;AAAE,YAAA,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC;IACvD;;IAGA,iBAAiB,GAAA;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE;IAChE;AAEA,IAAA,QAAQ,CAAC,KAAa,EAAA;AACpB,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,QAAQ,EAAE,KAAK,KAAK,CAAC;IAC3D;IAEA,OAAO,GAAA;AACL,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE;AACvB,QAAA,IAAI,CAAC,OAAO,GAAG,SAAS;IAC1B;AACD;;AC/CD;;;;;;;;;;;;;;;;;;;;;;AAsBG;MAOU,QAAQ,CAAA;;AAEV,IAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;;AAE5B,IAAA,UAAU,GAAG,KAAK,CAAyB,WAAW,iFAAC;;IAEvD,cAAc,GAAG,MAAM,EAAU;AAEzB,IAAA,GAAG,GAAG,MAAM,CAAC,eAAe,CAAC;IAC7B,IAAI,GAAG,eAAe,CAAC,OAAO,4EAAI,WAAW,EAAE,IAAI,EAAA,CAAG;IACtD,MAAM,GAAG,eAAe,CAAC,YAAY,8EACpD,WAAW,EAAE,IAAI,EAAA,CACjB;AACe,IAAA,QAAQ,GAAG,IAAI,gBAAgB,EAAE;AAElD,IAAA,WAAA,GAAA;;AAEE,QAAA,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;AAClD,QAAA,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;;AAGhE,QAAA,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;AACpD,QAAA,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;;;QAIxD,MAAM,CAAC,MAAK;YACV,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,MAAM,EAAE;AACf,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,EAAE;YACjD,MAAM,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC;AAEA,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IAC7D;AAEA;AACkF;IAC1E,MAAM,GAAA;AACZ,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG;AACrC,cAAE;eACC,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;AAC9B,QAAA,IAAI,IAAI;AAAE,YAAA,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC;IACzC;AAEA;AACyG;AACjG,IAAA,SAAS,CAAC,KAAa,EAAA;QAC7B,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;AAC/B,QAAA,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;AAC9B,QAAA,IAAI,IAAI,CAAC,QAAQ,EAAE,KAAK,KAAK;AAAE,YAAA,OAAO;AACtC,QAAA,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;IACjC;AAEQ,IAAA,gBAAgB,CAAC,KAAoB,EAAA;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE;AAC1C,QAAA,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE;AACzC,QAAA,IAAI,KAAK,IAAI,KAAK,KAAK,MAAM,EAAE;YAC7B,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAChC,YAAA,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,WAAW;AAAE,gBAAA,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAClE;IACF;;IAGQ,cAAc,GAAA;QACpB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC5D,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;AAChE,QAAA,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE;AACzB,YAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;AACrB,gBAAA,OAAO,CAAC,IAAI,CACV,yBAAyB,CAAC,CAAA,+EAAA,CAAiF,CAC5G;QACL;AACA,QAAA,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE;AAC3B,YAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AACnB,gBAAA,OAAO,CAAC,IAAI,CACV,2BAA2B,CAAC,CAAA,8DAAA,CAAgE,CAC7F;QACL;IACF;uGApFW,QAAQ,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAR,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,QAAQ,4XAFR,CAAC,eAAe,CAAC,EAAA,OAAA,EAAA,CAAA,EAAA,YAAA,EAAA,MAAA,EAAA,SAAA,EAWY,OAAO,4EACL,YAAY,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,CAAA,UAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAV3C,QAAQ,EAAA,UAAA,EAAA,CAAA;kBANpB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,YAAY;AACtB,oBAAA,UAAU,EAAE,IAAI;AAChB,oBAAA,QAAQ,EAAE,UAAU;oBACpB,SAAS,EAAE,CAAC,eAAe,CAAC;AAC7B,iBAAA;2XAUyC,OAAO,CAAA,EAAA,EAAA,GAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,eAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,UAAA,CAAA,MAC5B,YAAY,CAAA,EAAA,EAAA,GAAE;AACtD,4BAAA,WAAW,EAAE,IAAI;AAClB,yBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AC/CH;;;;;AAKG;MAeU,WAAW,CAAA;IACL,GAAG,GAAG,MAAM,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAElE,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,EAAE;AACjD,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;gBACb,OAAO,CAAC,IAAI,CACV,oFAAoF;AAClF,oBAAA,kDAAkD,CACrD;YACH;QACF;IACF;AAEU,IAAA,SAAS,CAAC,KAAoB,EAAA;AACtC,QAAA,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC;IAChC;uGAhBW,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAX,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,WAAW,kOAVZ,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,wOAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAU1B,WAAW,EAAA,UAAA,EAAA,CAAA;kBAdvB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,eAAe,EAAA,UAAA,EACb,IAAI,EAAA,QAAA,EACN,aAAa,YACb,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;AACJ,wBAAA,IAAI,EAAE,SAAS;AACf,wBAAA,kBAAkB,EAAE,YAAY;AAChC,wBAAA,WAAW,EAAE,mBAAmB;AACjC,qBAAA,EAAA,MAAA,EAAA,CAAA,wOAAA,CAAA,EAAA;;;AC7BH;;AAEG;;;;"}