@skra/ui 0.8.0 → 0.9.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.
@@ -60,7 +60,7 @@ Violating these is a review blocker in a consumer repo just as inside Skra.
60
60
  | **File upload** | `file-upload.md` | Choosing files (picker / drop zone), a file list with progress, the queue model, a tus upload; `accept` and language limits | ✅ draft |
61
61
  | **Feedback** | `feedback.md` | Alert vs toast vs badge vs field error; `live` announcing; dismissal; an alert with a next-step action | ✅ draft |
62
62
  | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
63
- | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Surface / Grid; building a card; the gap scale | ✅ draft |
63
+ | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Surface / Grid; cards, nav links, the gap scale | ✅ draft |
64
64
  | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
65
65
  | **Custom components** | `custom-components.md` | Authoring a net-new widget Skra does not ship (nothing to wrap) on Skra tokens — own token tier, state, promotion path | ✅ draft |
66
66
 
@@ -11,8 +11,12 @@ a native host** (a bounded allowlist of `div`/`section`/`nav`/`ul`/…), **never
11
11
  `<skra-stack>` custom element. Put them on the semantic element you'd use anyway:
12
12
 
13
13
  ```html
14
- <section skraStack gap="md">…</section> <!-- not <div><skra-stack> -->
15
- <ul skraStack gap="sm">…</ul> <!-- list markers normalized, restorable -->
14
+ <section skraStack gap="md">…</section>
15
+ <!-- not <div><skra-stack> -->
16
+ <ul skraStack gap="sm">
17
+ …
18
+ </ul>
19
+ <!-- list markers normalized, restorable -->
16
20
  ```
17
21
 
18
22
  ## Sizing container-filling components (e.g. `skra-select`)
@@ -38,16 +42,16 @@ width.
38
42
 
39
43
  Arranges children in a row or column.
40
44
 
41
- | Input | Values | Default |
42
- | --- | --- | --- |
43
- | `direction` | `row` \| `column` | `column` |
44
- | `gap` | `none` \| `sm` \| `md` \| `lg` | `md` |
45
- | `align` | `start` \| `center` \| `end` \| `stretch` | `stretch` |
46
- | `justify` | `start` \| `center` \| `end` \| `between` \| `around` | `start` |
47
- | `wrap` | boolean | `false` |
45
+ | Input | Values | Default |
46
+ | ----------- | ------------------------------------------------------- | --------- |
47
+ | `direction` | `row` \| `column` | `column` |
48
+ | `gap` | `none` \| `sm` \| `md` \| `lg` | `md` |
49
+ | `align` | `start` \| `center` \| `end` \| `stretch` \| `baseline` | `stretch` |
50
+ | `justify` | `start` \| `center` \| `end` \| `between` \| `around` | `start` |
51
+ | `wrap` | boolean | `false` |
48
52
 
49
53
  ```html
50
- <div skraStack direction="row" gap="sm" wrap align="center">
54
+ <div skraStack direction="row" gap="sm" wrap [align]="'center'">
51
55
  <skra-chip>One</skra-chip>
52
56
  <skra-chip>Two</skra-chip>
53
57
  </div>
@@ -56,13 +60,24 @@ Arranges children in a row or column.
56
60
  (`wrap` is a bare boolean attribute — every Skra boolean input carries `booleanAttribute`, so
57
61
  the bare form works; see `setup-and-gotchas.md`.)
58
62
 
63
+ **Gotcha — bind `align`, don't write it as a static attribute.** `align` is also a legacy HTML
64
+ attribute. Written statically (`<div skraStack align="center">`), Angular sets the input but the
65
+ attribute stays in the DOM, and the browser turns it into `text-align` on the host — Chromium
66
+ does so on every host Stack accepts (`div`, `section`, `header`, `ul`, …) and for both `center`
67
+ and `end`. Any text inside the stack is then centred or end-aligned. The bound form
68
+ `[align]="'center'"` sets the input without writing the attribute. The same applies to
69
+ `skraGrid`'s `align`. Since O-89 the atoms neutralise the hint themselves (`text-align: inherit`
70
+ while the attribute is present), so the static form no longer misaligns text — the bound form
71
+ stays the recommended one. If you want aligned text inside a stack, set `text-align` on a child
72
+ (or on the host with a selector more specific than a single class).
73
+
59
74
  ## Box — padding / spacing (`[skraBox]`)
60
75
 
61
76
  A semantically-neutral padding primitive. Does **not** arrange children (no list hosts).
62
77
 
63
- | Input | Values | Default |
64
- | --- | --- | --- |
65
- | `padding` | `none` \| `sm` \| `md` \| `lg` | `none` |
78
+ | Input | Values | Default |
79
+ | --------- | ----------------------------------- | ------- |
80
+ | `padding` | `none` \| `sm` \| `md` \| `lg` | `none` |
66
81
  | `display` | `block` \| `inline-block` \| `flex` | `block` |
67
82
 
68
83
  ```html
@@ -74,8 +89,8 @@ A semantically-neutral padding primitive. Does **not** arrange children (no list
74
89
  Box's **painting** sibling: background, border, radius, shadow — plus its own padding. Same
75
90
  host allowlist as Box (`div`/`section`/`article`/`main`/`nav`/`aside`/`header`/`footer`/`figure`).
76
91
 
77
- | Input | Values | Default |
78
- | --- | --- | --- |
92
+ | Input | Values | Default |
93
+ | --------- | ------------------------------ | -------- |
79
94
  | `padding` | `none` \| `sm` \| `md` \| `lg` | **`md`** |
80
95
 
81
96
  That is the whole API. Note the default differs from Box's `none` — Box is spacing-neutral by
@@ -109,16 +124,16 @@ There is no `selected` input, and there never will be one — `aria-selected` is
109
124
  producing invalid ARIA. Instead, Surface paints a selected state when **your own markup** says
110
125
  so, from exactly two signals:
111
126
 
112
- | Your markup | When it is the right one |
113
- | --- | --- |
114
- | `aria-current="true"` (or `page` / `step` / `date`) on the Surface host | the current item of a set — a global ARIA state, so it is valid on a role-less `<article>` |
115
- | `aria-selected="true"` on a host that **already** carries `option`/`row`/`gridcell`/`tab` | only inside a listbox / grid / tablist you actually built |
127
+ | Your markup | When it is the right one |
128
+ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
129
+ | `aria-current="true"` (or `page` / `step` / `date`) on the Surface host | the current item of a set — a global ARIA state, so it is valid on a role-less `<article>` |
130
+ | `aria-selected="true"` on a host that **already** carries `option`/`row`/`gridcell`/`tab` | only inside a listbox / grid / tablist you actually built |
116
131
 
117
132
  ```html
118
133
  <article skraSurface padding="md" aria-current="true" aria-labelledby="t">…</article>
119
134
  ```
120
135
 
121
- `aria-current="false"` and `aria-selected="false"` are explicit *not*-current and paint as
136
+ `aria-current="false"` and `aria-selected="false"` are explicit _not_-current and paint as
122
137
  resting.
123
138
 
124
139
  **Gotcha — a checked control inside the card is NOT a signal.** A card wrapping a checked
@@ -133,34 +148,97 @@ there: the radio already announces "selected", and adding `aria-current` makes a
133
148
  technology announce one fact twice under two names. A dedicated marker for that shape is a known
134
149
  gap (Skra-side `[skraSurfaceSelect]`), not something to emulate with a class.
135
150
 
136
- ### Interactive (clickable) cards are not shipped yet
151
+ ### Interactive (clickable) cards — `[skraSurfaceLink]`
137
152
 
138
153
  Surface is a container, not a widget, and the host allowlist deliberately excludes `a` and
139
- `button`: `<button>` cannot legally contain flow content (its content model is phrasing content
140
- with no interactive descendant), and an `<a>` wrapping the whole card takes the card's **entire**
141
- text as its accessible name — which `aria-label` may not fix (Iron Rule 3).
154
+ `button`: `<button>` cannot legally contain flow content, and an `<a>` wrapping the whole card
155
+ takes the card's **entire** text as its accessible name — which `aria-label` may not fix
156
+ (Iron Rule 3). Instead, put `skraSurfaceLink` on **your own** primary link (or `<button>`, when
157
+ the card performs an action) inside the Surface:
158
+
159
+ ```html
160
+ <article skraSurface padding="lg">
161
+ <h3 skraTypography="title-small">
162
+ <a skraSurfaceLink routerLink="/runs/2026-08-24">Morning run</a>
163
+ </h3>
164
+ <p skraTypography="body-medium">5.2 km · 24:31</p>
165
+ </article>
166
+ ```
167
+
168
+ What you get, with no inputs:
169
+
170
+ - The link's hit area covers the whole card; one tab stop; the accessible name is the link
171
+ text ("Morning run"), not the whole card body.
172
+ - Hover fills the card (pointer devices only — it does not latch after a tap), and the focus
173
+ ring is drawn **around the card**, the real target.
174
+ - The link text takes the card's text colour, without an underline (a skin may change that).
142
175
 
143
- Until the stretched-link marker ships, put a normal `a[skraLink]`/`button[skraButton]` **inside**
144
- the card and let it be the target. Do not make the card itself clickable with a `(click)` on a
145
- `<div>` — that fails keyboard access (WCAG 2.1.1) and has no role.
176
+ Rules:
177
+
178
+ - **One `skraSurfaceLink` per Surface.** A second one, or one with no Surface ancestor, is a
179
+ dev-mode console error. A list of targets is a list of Surfaces — `<li><article skraSurface>…` — and
180
+ a Surface nested in another paints only itself; the outer panel does not light up.
181
+ - **`skraLink` + `skraSurfaceLink` on one `<a>` is not possible** (both are components). The
182
+ marker already carries the text look.
183
+ - **A second control inside a linked card** (a "Share" button) sits under the overlay unless
184
+ you raise it: give it `position: relative` and keep it after the link in the DOM. Keep it at
185
+ least 24×24 CSS px (WCAG 2.5.8).
186
+ - Text inside a linked card cannot be selected with the mouse — a known, accepted cost of the
187
+ pattern.
188
+ - Do not make a card clickable with `(click)` on a `<div>` — that fails keyboard access
189
+ (WCAG 2.1.1) and has no role.
190
+
191
+ **Hover without a link:** an informational card has no hover state, and there is no
192
+ `hoverable` input. If you add your own hover paint in app CSS, gate it behind
193
+ `@media (hover: hover) and (pointer: fine)`, or it will latch after every tap on touch.
194
+
195
+ ## Navigation — `a[skraNavLink]` (ADR 0060)
196
+
197
+ A primary or section navigation whose **current** destination is visible, not only announced.
198
+ `skraNavLink` is a link — middle-click, "open in new tab", "copy link" all stay — with a
199
+ surface shape (pill or square, the skin decides) and a current state:
200
+
201
+ ```html
202
+ <nav aria-label="Primary">
203
+ <ul skraStack direction="row" gap="sm">
204
+ <li>
205
+ <a skraNavLink routerLink="/groups" routerLinkActive ariaCurrentWhenActive="page">Groups</a>
206
+ </li>
207
+ <li>
208
+ <a skraNavLink routerLink="/runs" routerLinkActive ariaCurrentWhenActive="page">Runs</a>
209
+ </li>
210
+ </ul>
211
+ </nav>
212
+ ```
146
213
 
147
- **Hover:** Surface has no hover state in v1, and there is no `hoverable` input — an
148
- informational card correctly stays inert. If you add your own hover paint in app CSS, gate it
149
- behind `@media (hover: hover) and (pointer: fine)`, or it will latch after every tap on touch.
214
+ - **The current state comes from `aria-current`, which you (or the router) write.**
215
+ `skraNavLink` has no `active` input — it reads the attribute, so what the eye sees and what a
216
+ screen reader announces cannot disagree. Without `routerLinkActive` (or your own
217
+ `[attr.aria-current]`), no item ever shows as current. `aria-current="false"` is not current.
218
+ - **It must sit inside `<nav>`** (or `role="navigation"`); outside one it is a dev-mode console
219
+ error. A pill-shaped link in a hero section is a marketing CTA, which Skra does not ship
220
+ (ADR 032) — use `skraButton` for an action, `skraLink` for a link in text.
221
+ - **No `intent`, `emphasis`, `size` or `disabled`.** A destination the user cannot visit is not a
222
+ link: leave it out of the nav, or render it as plain text.
223
+ - **Name = the visible text.** Do not add `aria-label`.
224
+ - Spacing between items is the list's `skraStack` gap; for a side nav use
225
+ `direction="column"`.
226
+ - `skraLink` vs `skraNavLink`: underlined text in a sentence → `skraLink`; a destination in a
227
+ navigation bar → `skraNavLink`. Do not put both on one `<a>` (both are components).
150
228
 
151
229
  ## Grid — 2D (`[skraGrid]`)
152
230
 
153
231
  Bucket-only CSS Grid. The default `columns="auto-fit"` + `min` gives an intrinsically
154
232
  responsive card grid (column count tracks width, **no media/container queries**):
155
233
 
156
- | Input | Values | Default |
157
- | --- | --- | --- |
158
- | `columns` | `1`…`6` \| `auto-fit` \| `auto-fill` | `auto-fit` |
159
- | `min` | `sm` \| `md` \| `lg` | — |
160
- | `gap` | `none` \| `sm` \| `md` \| `lg` | — |
161
- | `flow` | `row` \| `column` \| `dense` | — |
162
- | `align` | `start` \| `center` \| `end` \| `stretch` | — |
163
- | `justifyItems` | `start` \| `center` \| `end` \| `stretch` | — |
234
+ | Input | Values | Default |
235
+ | -------------- | ------------------------------------------------------------------------------------------------ | ---------- |
236
+ | `columns` | `1`…`6` \| `auto-fit` \| `auto-fill` | `auto-fit` |
237
+ | `min` | `sm` \| `md` \| `lg` | — |
238
+ | `gap` | `none` \| `sm` \| `md` \| `lg` | — |
239
+ | `flow` | `row` \| `column` \| `dense` | — |
240
+ | `align` | `start` \| `center` \| `end` \| `stretch` — bind it: `[align]="'center'"` (see the Stack gotcha) | — |
241
+ | `justifyItems` | `start` \| `center` \| `end` \| `stretch` | — |
164
242
 
165
243
  ```html
166
244
  <div skraGrid columns="auto-fit" min="md" gap="md">
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, ViewEncapsulation, ChangeDetectionStrategy, Component, booleanAttribute } from '@angular/core';
2
+ import { input, ViewEncapsulation, ChangeDetectionStrategy, Component, booleanAttribute, inject, ElementRef, afterNextRender } from '@angular/core';
3
3
 
4
4
  /**
5
5
  * SkraBox — semantically-neutral spacing/padding primitive (ADR 041).
@@ -56,7 +56,7 @@ class SkraStack {
56
56
  ...(ngDevMode ? [{ debugName: "justify" }] : /* istanbul ignore next */ []));
57
57
  wrap = input(false, { ...(ngDevMode ? { debugName: "wrap" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
58
58
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraStack, deps: [], target: i0.ɵɵFactoryTarget.Component });
59
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: SkraStack, isStandalone: true, selector: "div[skraStack], section[skraStack], article[skraStack], main[skraStack], nav[skraStack], aside[skraStack], header[skraStack], footer[skraStack], form[skraStack], figure[skraStack], ul[skraStack], ol[skraStack], menu[skraStack]", inputs: { direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-direction": "direction()", "attr.data-skra-gap": "gap()", "attr.data-skra-align": "align()", "attr.data-skra-justify": "justify()", "attr.data-skra-wrap": "wrap() ? \"\" : null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:flex;margin:0;box-sizing:border-box}:host[data-skra-direction=column]{flex-direction:column}:host[data-skra-direction=row]{flex-direction:row}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-stack-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-stack-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-stack-gap-lg)}:host[data-skra-align=start]{align-items:flex-start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:flex-end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-justify=start]{justify-content:flex-start}:host[data-skra-justify=center]{justify-content:center}:host[data-skra-justify=end]{justify-content:flex-end}:host[data-skra-justify=between]{justify-content:space-between}:host[data-skra-justify=around]{justify-content:space-around}:host[data-skra-wrap]{flex-wrap:wrap}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
59
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: SkraStack, isStandalone: true, selector: "div[skraStack], section[skraStack], article[skraStack], main[skraStack], nav[skraStack], aside[skraStack], header[skraStack], footer[skraStack], form[skraStack], figure[skraStack], ul[skraStack], ol[skraStack], menu[skraStack]", inputs: { direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justify: { classPropertyName: "justify", publicName: "justify", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-direction": "direction()", "attr.data-skra-gap": "gap()", "attr.data-skra-align": "align()", "attr.data-skra-justify": "justify()", "attr.data-skra-wrap": "wrap() ? \"\" : null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:flex;margin:0;box-sizing:border-box}:host[data-skra-direction=column]{flex-direction:column}:host[data-skra-direction=row]{flex-direction:row}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-stack-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-stack-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-stack-gap-lg)}:host[data-skra-align=start]{align-items:flex-start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:flex-end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-align=baseline]{align-items:baseline}:host[data-skra-justify=start]{justify-content:flex-start}:host[data-skra-justify=center]{justify-content:center}:host[data-skra-justify=end]{justify-content:flex-end}:host[data-skra-justify=between]{justify-content:space-between}:host[data-skra-justify=around]{justify-content:space-around}:host[data-skra-wrap]{flex-wrap:wrap}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}:host(:where([align])){text-align:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
60
60
  }
61
61
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraStack, decorators: [{
62
62
  type: Component,
@@ -70,7 +70,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
70
70
  '[attr.data-skra-align]': 'align()',
71
71
  '[attr.data-skra-justify]': 'justify()',
72
72
  '[attr.data-skra-wrap]': 'wrap() ? "" : null',
73
- }, styles: [":host{display:flex;margin:0;box-sizing:border-box}:host[data-skra-direction=column]{flex-direction:column}:host[data-skra-direction=row]{flex-direction:row}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-stack-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-stack-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-stack-gap-lg)}:host[data-skra-align=start]{align-items:flex-start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:flex-end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-justify=start]{justify-content:flex-start}:host[data-skra-justify=center]{justify-content:center}:host[data-skra-justify=end]{justify-content:flex-end}:host[data-skra-justify=between]{justify-content:space-between}:host[data-skra-justify=around]{justify-content:space-around}:host[data-skra-wrap]{flex-wrap:wrap}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}\n"] }]
73
+ }, styles: [":host{display:flex;margin:0;box-sizing:border-box}:host[data-skra-direction=column]{flex-direction:column}:host[data-skra-direction=row]{flex-direction:row}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-stack-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-stack-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-stack-gap-lg)}:host[data-skra-align=start]{align-items:flex-start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:flex-end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-align=baseline]{align-items:baseline}:host[data-skra-justify=start]{justify-content:flex-start}:host[data-skra-justify=center]{justify-content:center}:host[data-skra-justify=end]{justify-content:flex-end}:host[data-skra-justify=between]{justify-content:space-between}:host[data-skra-justify=around]{justify-content:space-around}:host[data-skra-wrap]{flex-wrap:wrap}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}:host(:where([align])){text-align:inherit}\n"] }]
74
74
  }], propDecorators: { direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], justify: [{ type: i0.Input, args: [{ isSignal: true, alias: "justify", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }] } });
75
75
 
76
76
  /**
@@ -105,7 +105,7 @@ class SkraGrid {
105
105
  justifyItems = input('stretch', /* @ts-ignore */
106
106
  ...(ngDevMode ? [{ debugName: "justifyItems" }] : /* istanbul ignore next */ []));
107
107
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
108
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: SkraGrid, isStandalone: true, selector: "div[skraGrid], section[skraGrid], article[skraGrid], main[skraGrid], nav[skraGrid], aside[skraGrid], header[skraGrid], footer[skraGrid], form[skraGrid], figure[skraGrid], ul[skraGrid], ol[skraGrid], menu[skraGrid]", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, flow: { classPropertyName: "flow", publicName: "flow", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justifyItems: { classPropertyName: "justifyItems", publicName: "justifyItems", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-columns": "columns()", "attr.data-skra-min": "min()", "attr.data-skra-gap": "gap()", "attr.data-skra-flow": "flow()", "attr.data-skra-align": "align()", "attr.data-skra-justify-items": "justifyItems()" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:grid;margin:0;box-sizing:border-box}:host[data-skra-min=sm]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-sm)}:host[data-skra-min=md]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-md)}:host[data-skra-min=lg]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-lg)}:host[data-skra-columns=\"1\"]{grid-template-columns:repeat(1,minmax(0,1fr))}:host[data-skra-columns=\"2\"]{grid-template-columns:repeat(2,minmax(0,1fr))}:host[data-skra-columns=\"3\"]{grid-template-columns:repeat(3,minmax(0,1fr))}:host[data-skra-columns=\"4\"]{grid-template-columns:repeat(4,minmax(0,1fr))}:host[data-skra-columns=\"5\"]{grid-template-columns:repeat(5,minmax(0,1fr))}:host[data-skra-columns=\"6\"]{grid-template-columns:repeat(6,minmax(0,1fr))}:host[data-skra-columns=auto-fit]{grid-template-columns:repeat(auto-fit,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-columns=auto-fill]{grid-template-columns:repeat(auto-fill,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-grid-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-grid-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-grid-gap-lg)}:host[data-skra-flow=row]{grid-auto-flow:row}:host[data-skra-flow=column]{grid-auto-flow:column}:host[data-skra-flow=dense]{grid-auto-flow:row dense}:host[data-skra-align=start]{align-items:start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-justify-items=start]{justify-items:start}:host[data-skra-justify-items=center]{justify-items:center}:host[data-skra-justify-items=end]{justify-items:end}:host[data-skra-justify-items=stretch]{justify-items:stretch}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
108
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: SkraGrid, isStandalone: true, selector: "div[skraGrid], section[skraGrid], article[skraGrid], main[skraGrid], nav[skraGrid], aside[skraGrid], header[skraGrid], footer[skraGrid], form[skraGrid], figure[skraGrid], ul[skraGrid], ol[skraGrid], menu[skraGrid]", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, flow: { classPropertyName: "flow", publicName: "flow", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, justifyItems: { classPropertyName: "justifyItems", publicName: "justifyItems", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-columns": "columns()", "attr.data-skra-min": "min()", "attr.data-skra-gap": "gap()", "attr.data-skra-flow": "flow()", "attr.data-skra-align": "align()", "attr.data-skra-justify-items": "justifyItems()" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:grid;margin:0;box-sizing:border-box}:host[data-skra-min=sm]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-sm)}:host[data-skra-min=md]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-md)}:host[data-skra-min=lg]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-lg)}:host[data-skra-columns=\"1\"]{grid-template-columns:repeat(1,minmax(0,1fr))}:host[data-skra-columns=\"2\"]{grid-template-columns:repeat(2,minmax(0,1fr))}:host[data-skra-columns=\"3\"]{grid-template-columns:repeat(3,minmax(0,1fr))}:host[data-skra-columns=\"4\"]{grid-template-columns:repeat(4,minmax(0,1fr))}:host[data-skra-columns=\"5\"]{grid-template-columns:repeat(5,minmax(0,1fr))}:host[data-skra-columns=\"6\"]{grid-template-columns:repeat(6,minmax(0,1fr))}:host[data-skra-columns=auto-fit]{grid-template-columns:repeat(auto-fit,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-columns=auto-fill]{grid-template-columns:repeat(auto-fill,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-grid-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-grid-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-grid-gap-lg)}:host[data-skra-flow=row]{grid-auto-flow:row}:host[data-skra-flow=column]{grid-auto-flow:column}:host[data-skra-flow=dense]{grid-auto-flow:row dense}:host[data-skra-align=start]{align-items:start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-justify-items=start]{justify-items:start}:host[data-skra-justify-items=center]{justify-items:center}:host[data-skra-justify-items=end]{justify-items:end}:host[data-skra-justify-items=stretch]{justify-items:stretch}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}:host(:where([align])){text-align:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
109
109
  }
110
110
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraGrid, decorators: [{
111
111
  type: Component,
@@ -120,7 +120,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
120
120
  '[attr.data-skra-flow]': 'flow()',
121
121
  '[attr.data-skra-align]': 'align()',
122
122
  '[attr.data-skra-justify-items]': 'justifyItems()',
123
- }, styles: [":host{display:grid;margin:0;box-sizing:border-box}:host[data-skra-min=sm]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-sm)}:host[data-skra-min=md]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-md)}:host[data-skra-min=lg]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-lg)}:host[data-skra-columns=\"1\"]{grid-template-columns:repeat(1,minmax(0,1fr))}:host[data-skra-columns=\"2\"]{grid-template-columns:repeat(2,minmax(0,1fr))}:host[data-skra-columns=\"3\"]{grid-template-columns:repeat(3,minmax(0,1fr))}:host[data-skra-columns=\"4\"]{grid-template-columns:repeat(4,minmax(0,1fr))}:host[data-skra-columns=\"5\"]{grid-template-columns:repeat(5,minmax(0,1fr))}:host[data-skra-columns=\"6\"]{grid-template-columns:repeat(6,minmax(0,1fr))}:host[data-skra-columns=auto-fit]{grid-template-columns:repeat(auto-fit,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-columns=auto-fill]{grid-template-columns:repeat(auto-fill,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-grid-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-grid-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-grid-gap-lg)}:host[data-skra-flow=row]{grid-auto-flow:row}:host[data-skra-flow=column]{grid-auto-flow:column}:host[data-skra-flow=dense]{grid-auto-flow:row dense}:host[data-skra-align=start]{align-items:start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-justify-items=start]{justify-items:start}:host[data-skra-justify-items=center]{justify-items:center}:host[data-skra-justify-items=end]{justify-items:end}:host[data-skra-justify-items=stretch]{justify-items:stretch}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}\n"] }]
123
+ }, styles: [":host{display:grid;margin:0;box-sizing:border-box}:host[data-skra-min=sm]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-sm)}:host[data-skra-min=md]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-md)}:host[data-skra-min=lg]{--skra-comp-grid-track-min: var(--skra-comp-grid-min-lg)}:host[data-skra-columns=\"1\"]{grid-template-columns:repeat(1,minmax(0,1fr))}:host[data-skra-columns=\"2\"]{grid-template-columns:repeat(2,minmax(0,1fr))}:host[data-skra-columns=\"3\"]{grid-template-columns:repeat(3,minmax(0,1fr))}:host[data-skra-columns=\"4\"]{grid-template-columns:repeat(4,minmax(0,1fr))}:host[data-skra-columns=\"5\"]{grid-template-columns:repeat(5,minmax(0,1fr))}:host[data-skra-columns=\"6\"]{grid-template-columns:repeat(6,minmax(0,1fr))}:host[data-skra-columns=auto-fit]{grid-template-columns:repeat(auto-fit,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-columns=auto-fill]{grid-template-columns:repeat(auto-fill,minmax(var(--skra-comp-grid-track-min),1fr))}:host[data-skra-gap=none]{gap:0}:host[data-skra-gap=sm]{gap:var(--skra-comp-grid-gap-sm)}:host[data-skra-gap=md]{gap:var(--skra-comp-grid-gap-md)}:host[data-skra-gap=lg]{gap:var(--skra-comp-grid-gap-lg)}:host[data-skra-flow=row]{grid-auto-flow:row}:host[data-skra-flow=column]{grid-auto-flow:column}:host[data-skra-flow=dense]{grid-auto-flow:row dense}:host[data-skra-align=start]{align-items:start}:host[data-skra-align=center]{align-items:center}:host[data-skra-align=end]{align-items:end}:host[data-skra-align=stretch]{align-items:stretch}:host[data-skra-justify-items=start]{justify-items:start}:host[data-skra-justify-items=center]{justify-items:center}:host[data-skra-justify-items=end]{justify-items:end}:host[data-skra-justify-items=stretch]{justify-items:stretch}:host(ul),:host(ol),:host(menu){list-style:none;padding-inline-start:0}:host(:where([align])){text-align:inherit}\n"] }]
124
124
  }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], flow: [{ type: i0.Input, args: [{ isSignal: true, alias: "flow", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], justifyItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "justifyItems", required: false }] }] } });
125
125
 
126
126
  /**
@@ -151,7 +151,7 @@ class SkraSurface {
151
151
  padding = input('md', /* @ts-ignore */
152
152
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
153
153
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSurface, deps: [], target: i0.ɵɵFactoryTarget.Component });
154
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: SkraSurface, isStandalone: true, selector: "div[skraSurface], section[skraSurface], article[skraSurface], main[skraSurface], nav[skraSurface], aside[skraSurface], header[skraSurface], footer[skraSurface], figure[skraSurface]", inputs: { padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-padding": "padding()" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:block;margin:0;box-sizing:border-box;background:var(--skra-comp-surface-background);color:var(--skra-comp-surface-foreground);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width);border-style:var(--skra-comp-surface-border-style);border-radius:var(--skra-comp-surface-radius);box-shadow:var(--skra-comp-surface-shadow);padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline);transition:background-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing),border-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing)}:host[data-skra-padding=none]{padding-block:0;padding-inline:0}:host[data-skra-padding=sm]{padding-block:var(--skra-comp-surface-padding-block-sm);padding-inline:var(--skra-comp-surface-padding-inline-sm)}:host[data-skra-padding=md]{padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline)}:host[data-skra-padding=lg]{padding-block:var(--skra-comp-surface-padding-block-lg);padding-inline:var(--skra-comp-surface-padding-inline-lg)}:host([aria-current]),:host([aria-selected=true]){background:var(--skra-comp-surface-background-selected);border-color:var(--skra-comp-surface-border-color-selected);border-width:var(--skra-comp-surface-border-width-selected)}:host([aria-current=false]),:host([aria-selected=false]){background:var(--skra-comp-surface-background);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width)}:host(:focus-visible){outline:var(--skra-comp-surface-focus-ring-width) solid var(--skra-comp-surface-focus-ring-color);outline-offset:var(--skra-comp-surface-focus-ring-width)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
154
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.1", type: SkraSurface, isStandalone: true, selector: "div[skraSurface], section[skraSurface], article[skraSurface], main[skraSurface], nav[skraSurface], aside[skraSurface], header[skraSurface], footer[skraSurface], figure[skraSurface]", inputs: { padding: { classPropertyName: "padding", publicName: "padding", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-padding": "padding()" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:block;margin:0;box-sizing:border-box;background:var(--skra-comp-surface-background);color:var(--skra-comp-surface-foreground);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width);border-style:var(--skra-comp-surface-border-style);border-radius:var(--skra-comp-surface-radius);box-shadow:var(--skra-comp-surface-shadow);padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline);transition:background-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing),border-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing)}:host[data-skra-padding=none]{padding-block:0;padding-inline:0}:host[data-skra-padding=sm]{padding-block:var(--skra-comp-surface-padding-block-sm);padding-inline:var(--skra-comp-surface-padding-inline-sm)}:host[data-skra-padding=md]{padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline)}:host[data-skra-padding=lg]{padding-block:var(--skra-comp-surface-padding-block-lg);padding-inline:var(--skra-comp-surface-padding-inline-lg)}:host([aria-current]),:host([aria-selected=true]){background:var(--skra-comp-surface-background-selected);border-color:var(--skra-comp-surface-border-color-selected);border-width:var(--skra-comp-surface-border-width-selected)}:host([aria-current=false]),:host([aria-current=\"\"]),:host([aria-current=undefined]),:host([aria-selected=false]){background:var(--skra-comp-surface-background);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width)}:host(:focus-visible){outline:var(--skra-comp-surface-focus-ring-width) solid var(--skra-comp-surface-focus-ring-color);outline-offset:var(--skra-comp-surface-focus-ring-width)}:host(:has([skraSurfaceLink])){position:relative}@media(hover:hover)and (pointer:fine){:host(:has([skraSurfaceLink]:hover):not(:has([skraSurface] [skraSurfaceLink]:hover)):not([aria-current]:not([aria-current=false]):not([aria-current=\"\"]):not([aria-current=undefined])):not([aria-selected=true])){background:var(--skra-comp-surface-background-hover)}}:host(:has([skraSurfaceLink]:focus-visible):not(:has([skraSurface] [skraSurfaceLink]:focus-visible))){outline:var(--skra-comp-surface-focus-ring-width) solid var(--skra-comp-surface-focus-ring-color);outline-offset:var(--skra-comp-surface-focus-ring-width)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
155
155
  }
156
156
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSurface, decorators: [{
157
157
  type: Component,
@@ -159,12 +159,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
159
159
  // CSP-Safe State Reflection (ADR 021) — NO inline styles. This is the ONLY
160
160
  // host attribute Surface emits (ADR 0055 R2): no role, no tabindex, no aria-*.
161
161
  '[attr.data-skra-padding]': 'padding()',
162
- }, styles: [":host{display:block;margin:0;box-sizing:border-box;background:var(--skra-comp-surface-background);color:var(--skra-comp-surface-foreground);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width);border-style:var(--skra-comp-surface-border-style);border-radius:var(--skra-comp-surface-radius);box-shadow:var(--skra-comp-surface-shadow);padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline);transition:background-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing),border-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing)}:host[data-skra-padding=none]{padding-block:0;padding-inline:0}:host[data-skra-padding=sm]{padding-block:var(--skra-comp-surface-padding-block-sm);padding-inline:var(--skra-comp-surface-padding-inline-sm)}:host[data-skra-padding=md]{padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline)}:host[data-skra-padding=lg]{padding-block:var(--skra-comp-surface-padding-block-lg);padding-inline:var(--skra-comp-surface-padding-inline-lg)}:host([aria-current]),:host([aria-selected=true]){background:var(--skra-comp-surface-background-selected);border-color:var(--skra-comp-surface-border-color-selected);border-width:var(--skra-comp-surface-border-width-selected)}:host([aria-current=false]),:host([aria-selected=false]){background:var(--skra-comp-surface-background);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width)}:host(:focus-visible){outline:var(--skra-comp-surface-focus-ring-width) solid var(--skra-comp-surface-focus-ring-color);outline-offset:var(--skra-comp-surface-focus-ring-width)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
162
+ }, styles: [":host{display:block;margin:0;box-sizing:border-box;background:var(--skra-comp-surface-background);color:var(--skra-comp-surface-foreground);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width);border-style:var(--skra-comp-surface-border-style);border-radius:var(--skra-comp-surface-radius);box-shadow:var(--skra-comp-surface-shadow);padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline);transition:background-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing),border-color var(--skra-comp-surface-motion-duration) var(--skra-comp-surface-motion-easing)}:host[data-skra-padding=none]{padding-block:0;padding-inline:0}:host[data-skra-padding=sm]{padding-block:var(--skra-comp-surface-padding-block-sm);padding-inline:var(--skra-comp-surface-padding-inline-sm)}:host[data-skra-padding=md]{padding-block:var(--skra-comp-surface-padding-block);padding-inline:var(--skra-comp-surface-padding-inline)}:host[data-skra-padding=lg]{padding-block:var(--skra-comp-surface-padding-block-lg);padding-inline:var(--skra-comp-surface-padding-inline-lg)}:host([aria-current]),:host([aria-selected=true]){background:var(--skra-comp-surface-background-selected);border-color:var(--skra-comp-surface-border-color-selected);border-width:var(--skra-comp-surface-border-width-selected)}:host([aria-current=false]),:host([aria-current=\"\"]),:host([aria-current=undefined]),:host([aria-selected=false]){background:var(--skra-comp-surface-background);border-color:var(--skra-comp-surface-border-color);border-width:var(--skra-comp-surface-border-width)}:host(:focus-visible){outline:var(--skra-comp-surface-focus-ring-width) solid var(--skra-comp-surface-focus-ring-color);outline-offset:var(--skra-comp-surface-focus-ring-width)}:host(:has([skraSurfaceLink])){position:relative}@media(hover:hover)and (pointer:fine){:host(:has([skraSurfaceLink]:hover):not(:has([skraSurface] [skraSurfaceLink]:hover)):not([aria-current]:not([aria-current=false]):not([aria-current=\"\"]):not([aria-current=undefined])):not([aria-selected=true])){background:var(--skra-comp-surface-background-hover)}}:host(:has([skraSurfaceLink]:focus-visible):not(:has([skraSurface] [skraSurfaceLink]:focus-visible))){outline:var(--skra-comp-surface-focus-ring-width) solid var(--skra-comp-surface-focus-ring-color);outline-offset:var(--skra-comp-surface-focus-ring-width)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
163
163
  }], propDecorators: { padding: [{ type: i0.Input, args: [{ isSignal: true, alias: "padding", required: false }] }] } });
164
164
 
165
+ /**
166
+ * SkraSurfaceLink — the stretched-link marker that makes a whole `SkraSurface`
167
+ * one activation target (ADR 0055 R3/R4).
168
+ *
169
+ * Placed on the consumer's OWN primary control — `<a>` for navigation, `<button>`
170
+ * where the destination is an action — never on the Surface host (R3.1:
171
+ * `a[skraSurface]` / `button[skraSurface]` stay unsupported). Its own host
172
+ * stylesheet stretches the control's hit area over the nearest Surface with a
173
+ * `::after` overlay; the Surface reads `:has([skraSurfaceLink]…)` and paints hover
174
+ * and the focus ring on itself, because the surface is the target (R4.2).
175
+ *
176
+ * Accessible name: the control's own visible text (ADR 033 Path 1) — normally the
177
+ * card's heading. No inputs, no `aria-*`, no `role`, no `tabindex`: the native
178
+ * host already is the one tab stop.
179
+ *
180
+ * Dev-mode console errors (R3.3): no `[skraSurface]` ancestor (the overlay would
181
+ * stretch over whatever positioned ancestor happens to exist — possibly the whole
182
+ * page), and more than one marker whose NEAREST Surface is the same (two overlays
183
+ * = an invisible hit-area conflict). Checked on the rendered DOM, not through DI:
184
+ * a consumer's own card component projecting content into `<article skraSurface>`
185
+ * puts the marker inside the Surface in the DOM while its injector chain follows
186
+ * the declaring template and never meets the Surface.
187
+ */
188
+ class SkraSurfaceLink {
189
+ constructor() {
190
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
191
+ const host = inject(ElementRef).nativeElement;
192
+ // Browser-only (afterNextRender never runs on the server) and read-only: the
193
+ // DOM is inspected, never mutated.
194
+ afterNextRender(() => checkPlacement(host));
195
+ }
196
+ }
197
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSurfaceLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
198
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: SkraSurfaceLink, isStandalone: true, selector: "a[skraSurfaceLink], button[skraSurfaceLink]", ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{position:static;margin:0;color:inherit;text-decoration:var(--skra-comp-surface-link-text-decoration)}:host(button){padding:0;border:0;background:none;font:inherit;text-align:inherit;cursor:pointer}:host:after{content:\"\";position:absolute;inset:0}:host(:focus-visible){outline:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
199
+ }
200
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraSurfaceLink, decorators: [{
201
+ type: Component,
202
+ args: [{ selector: 'a[skraSurfaceLink], button[skraSurfaceLink]', standalone: true, template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, styles: [":host{position:static;margin:0;color:inherit;text-decoration:var(--skra-comp-surface-link-text-decoration)}:host(button){padding:0;border:0;background:none;font:inherit;text-align:inherit;cursor:pointer}:host:after{content:\"\";position:absolute;inset:0}:host(:focus-visible){outline:none}\n"] }]
203
+ }], ctorParameters: () => [] });
204
+ /** Markers already reported as duplicates, per Surface — so one conflict logs once. */
205
+ const reportedMarkers = new WeakMap();
206
+ /** Dev-mode placement check (ADR 0055 R3 / R3.3). */
207
+ function checkPlacement(host) {
208
+ const surface = host.parentElement?.closest('[skraSurface]');
209
+ if (!surface) {
210
+ console.error('[skraSurfaceLink] must sit inside a [skraSurface] host — its overlay stretches ' +
211
+ 'over the nearest Surface, and without one it would cover whatever positioned ' +
212
+ 'ancestor exists (ADR 0055 R3).', host);
213
+ return;
214
+ }
215
+ const own = Array.from(surface.querySelectorAll('[skraSurfaceLink]')).filter((link) => link.parentElement?.closest('[skraSurface]') === surface);
216
+ // Reported once per conflict, by whichever marker renders into it — including one
217
+ // inserted BEFORE an existing marker, which a "not the first in DOM order" rule
218
+ // missed (PR #9 review, S2). A marker that has already been reported stays quiet.
219
+ if (own.length > 1) {
220
+ const seen = reportedMarkers.get(surface) ?? new WeakSet();
221
+ reportedMarkers.set(surface, seen);
222
+ if (own.every((link) => seen.has(link)))
223
+ return;
224
+ own.forEach((link) => seen.add(link));
225
+ console.error('[skraSurfaceLink] appears more than once in the same [skraSurface] — at most one ' +
226
+ 'stretched link per surface (ADR 0055 R3.3). Make each target its own Surface, ' +
227
+ 'or keep the secondary control a plain link/button raised above the overlay.', host);
228
+ }
229
+ }
230
+
165
231
  /**
166
232
  * Generated bundle index. Do not edit.
167
233
  */
168
234
 
169
- export { SkraBox, SkraGrid, SkraStack, SkraSurface };
235
+ export { SkraBox, SkraGrid, SkraStack, SkraSurface, SkraSurfaceLink };
170
236
  //# sourceMappingURL=skra-ui-layout.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-layout.mjs","sources":["../../../../libs/ui/layout/src/lib/box/box.ts","../../../../libs/ui/layout/src/lib/stack/stack.ts","../../../../libs/ui/layout/src/lib/grid/grid.ts","../../../../libs/ui/layout/src/lib/surface/surface.ts","../../../../libs/ui/layout/src/skra-ui-layout.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\nexport type BoxPadding = 'none' | 'sm' | 'md' | 'lg';\nexport type BoxDisplay = 'block' | 'inline-block' | 'flex';\n\n/**\n * SkraBox — semantically-neutral spacing/padding primitive (ADR 041).\n *\n * Component-on-native-host: layers onto a generic flow/sectioning container via\n * the bounded host allowlist below — NEVER a `<skra-box>` custom element\n * (ADR 012 §3.1 / ADR 041). Box does NOT arrange children, so list hosts\n * (`ul`/`ol`/`menu`) are intentionally excluded from its allowlist.\n *\n * Box is a pure spacing primitive: it MUST NOT establish a container-query\n * context (`container-type`/`container-name`) — that is an app-level concern\n * (ADR 016 §3.3, amended by ADR 041).\n */\n@Component({\n selector:\n 'div[skraBox], section[skraBox], article[skraBox], main[skraBox], nav[skraBox], aside[skraBox], header[skraBox], footer[skraBox], form[skraBox], figure[skraBox]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './box.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection (ADR 021) — NO inline styles! The layout reset\n // (margin/box-sizing) + figure UA-margin normalization live in box.scss\n // `:host` (ADR 022 / ADR 041 Host Normalization Contract); host-metadata\n // bare keys would render as junk attributes, not styles.\n '[attr.data-skra-padding]': 'padding()',\n '[attr.data-skra-display]': 'display()',\n },\n})\nexport class SkraBox {\n readonly padding = input<BoxPadding>('none');\n readonly display = input<BoxDisplay>('block');\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n booleanAttribute,\n input,\n} from '@angular/core';\n\nexport type StackDirection = 'row' | 'column';\nexport type StackGap = 'none' | 'sm' | 'md' | 'lg';\nexport type StackAlign = 'start' | 'center' | 'end' | 'stretch';\nexport type StackJustify = 'start' | 'center' | 'end' | 'between' | 'around';\n\n/**\n * SkraStack — flex layout primitive that arranges its children (ADR 041).\n *\n * Component-on-native-host via the bounded allowlist below. As a child-arranger\n * it additionally accepts list hosts (`ul`/`ol`/`menu`) — their UA list styling\n * is normalized in stack.scss per the Host Normalization Contract. List markers\n * are restorable without `!important` via a direct `li {}` rule (a direct\n * declaration beats the inherited `list-style: none`).\n *\n * Stack consumes `@container`; it MUST NOT establish a container-query context\n * (ADR 016 §3.3, amended by ADR 041).\n */\n@Component({\n selector:\n 'div[skraStack], section[skraStack], article[skraStack], main[skraStack], nav[skraStack], aside[skraStack], header[skraStack], footer[skraStack], form[skraStack], figure[skraStack], ul[skraStack], ol[skraStack], menu[skraStack]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './stack.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection — only data-attributes, no [style.*]. The\n // layout reset (margin/box-sizing) + list/UA normalization live in\n // stack.scss `:host` (ADR 022 / ADR 041 Host Normalization Contract);\n // host-metadata bare keys would render as junk attributes, not styles.\n '[attr.data-skra-direction]': 'direction()',\n '[attr.data-skra-gap]': 'gap()',\n '[attr.data-skra-align]': 'align()',\n '[attr.data-skra-justify]': 'justify()',\n '[attr.data-skra-wrap]': 'wrap() ? \"\" : null',\n },\n})\nexport class SkraStack {\n readonly direction = input<StackDirection>('column');\n readonly gap = input<StackGap>('md');\n readonly align = input<StackAlign>('stretch');\n readonly justify = input<StackJustify>('start');\n readonly wrap = input(false, { transform: booleanAttribute });\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\nexport type GridColumns =\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | 'auto-fit'\n | 'auto-fill';\nexport type GridMin = 'sm' | 'md' | 'lg';\nexport type GridGap = 'none' | 'sm' | 'md' | 'lg';\nexport type GridFlow = 'row' | 'column' | 'dense';\nexport type GridAlign = 'start' | 'center' | 'end' | 'stretch';\n\n/**\n * SkraGrid — 2D layout primitive that arranges its children in a CSS Grid\n * (ADR 041, scope O-3). The bucket-only companion to Stack (1D) and Box (spacing).\n *\n * Component-on-native-host via the bounded allowlist below. As a child-arranger\n * it additionally accepts list hosts (`ul`/`ol`/`menu`) — their UA list styling\n * is normalized in grid.scss per the Host Normalization Contract.\n *\n * Bucket-only by design: every axis is an enumerated input reflected as a\n * `data-skra-*` attribute → token. Arbitrary track lists, `grid-template-areas`,\n * named lines and per-item placement are OUT of scope (bespoke = native CSS) —\n * exposing them would force an `[style.*]` host binding (ADR 021 violation).\n *\n * Responsiveness: `columns=\"auto-fit\"` (default) + `min` produce an intrinsically\n * responsive card grid via `repeat(auto-fit, minmax(var(--min), 1fr))` — column\n * count tracks available width with NO media/container queries. Grid consumes\n * `@container`; it MUST NOT establish a container-query context (ADR 041 §3.3).\n */\n@Component({\n selector:\n 'div[skraGrid], section[skraGrid], article[skraGrid], main[skraGrid], nav[skraGrid], aside[skraGrid], header[skraGrid], footer[skraGrid], form[skraGrid], figure[skraGrid], ul[skraGrid], ol[skraGrid], menu[skraGrid]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './grid.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection — only data-attributes, no [style.*]. The layout\n // reset (margin/box-sizing/display:grid) + list/UA normalization live in\n // grid.scss `:host` (ADR 022 / ADR 041 Host Normalization Contract);\n // host-metadata bare keys would render as junk attributes, not styles.\n '[attr.data-skra-columns]': 'columns()',\n '[attr.data-skra-min]': 'min()',\n '[attr.data-skra-gap]': 'gap()',\n '[attr.data-skra-flow]': 'flow()',\n '[attr.data-skra-align]': 'align()',\n '[attr.data-skra-justify-items]': 'justifyItems()',\n },\n})\nexport class SkraGrid {\n readonly columns = input<GridColumns>('auto-fit');\n readonly min = input<GridMin>('md');\n readonly gap = input<GridGap>('md');\n readonly flow = input<GridFlow>('row');\n readonly align = input<GridAlign>('stretch');\n readonly justifyItems = input<GridAlign>('stretch');\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\nexport type SurfacePadding = 'none' | 'sm' | 'md' | 'lg';\n\n/**\n * SkraSurface — the painted container primitive (ADR 0053), Box's painting sibling.\n *\n * Component-on-native-host: layers onto a generic flow/sectioning container via the\n * bounded host allowlist below — NEVER a `<skra-surface>` custom element, and never\n * an interactive host (ADR 0055 R3.1: `a[skraSurface]` / `button[skraSurface]` are\n * unsupported). \"Card\", \"Panel\" and \"Tile\" are consumer compositions of Surface +\n * `[skraStack]`/`[skraBox]` + a heading — not Skra components (ADR 0053 rule 1).\n *\n * **State-reading doctrine (ADR 0055).** Surface is a READER of state, never an\n * OWNER of state. It exposes no `interactive`/`hoverable`/`selected`/`elevation`\n * input and emits no `role`/`tabindex`/`aria-*`. Focus and selection are READ from\n * the markup the consumer authored (see surface.scss) — which is why an\n * informational card gets no interactive paint with zero API, and why a selected\n * card can never disagree with what assistive technology announces.\n *\n * Zero baked semantics: sectioning role and accessible name are 100% consumer-owned\n * (ADR 0053 rule 6). No `container-type` in v1 (ADR 0055 R7.1).\n */\n@Component({\n selector:\n 'div[skraSurface], section[skraSurface], article[skraSurface], main[skraSurface], nav[skraSurface], aside[skraSurface], header[skraSurface], footer[skraSurface], figure[skraSurface]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './surface.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection (ADR 021) — NO inline styles. This is the ONLY\n // host attribute Surface emits (ADR 0055 R2): no role, no tabindex, no aria-*.\n '[attr.data-skra-padding]': 'padding()',\n },\n})\nexport class SkraSurface {\n /**\n * Semantic padding preset, reusing Box's enum for cross-atom consistency\n * (ADR 0053 rule 4). Defaults to `md` rather than Box's `none` — Box is\n * spacing-neutral by construction, a painted container is not (ADR 0055 R1).\n */\n readonly padding = input<SurfacePadding>('md');\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skra-ui-layout.mjs","sources":["../../../../libs/ui/layout/src/lib/box/box.ts","../../../../libs/ui/layout/src/lib/stack/stack.ts","../../../../libs/ui/layout/src/lib/grid/grid.ts","../../../../libs/ui/layout/src/lib/surface/surface.ts","../../../../libs/ui/layout/src/lib/surface/surface-link.ts","../../../../libs/ui/layout/src/skra-ui-layout.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\nexport type BoxPadding = 'none' | 'sm' | 'md' | 'lg';\nexport type BoxDisplay = 'block' | 'inline-block' | 'flex';\n\n/**\n * SkraBox — semantically-neutral spacing/padding primitive (ADR 041).\n *\n * Component-on-native-host: layers onto a generic flow/sectioning container via\n * the bounded host allowlist below — NEVER a `<skra-box>` custom element\n * (ADR 012 §3.1 / ADR 041). Box does NOT arrange children, so list hosts\n * (`ul`/`ol`/`menu`) are intentionally excluded from its allowlist.\n *\n * Box is a pure spacing primitive: it MUST NOT establish a container-query\n * context (`container-type`/`container-name`) — that is an app-level concern\n * (ADR 016 §3.3, amended by ADR 041).\n */\n@Component({\n selector:\n 'div[skraBox], section[skraBox], article[skraBox], main[skraBox], nav[skraBox], aside[skraBox], header[skraBox], footer[skraBox], form[skraBox], figure[skraBox]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './box.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection (ADR 021) — NO inline styles! The layout reset\n // (margin/box-sizing) + figure UA-margin normalization live in box.scss\n // `:host` (ADR 022 / ADR 041 Host Normalization Contract); host-metadata\n // bare keys would render as junk attributes, not styles.\n '[attr.data-skra-padding]': 'padding()',\n '[attr.data-skra-display]': 'display()',\n },\n})\nexport class SkraBox {\n readonly padding = input<BoxPadding>('none');\n readonly display = input<BoxDisplay>('block');\n}\n","import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ViewEncapsulation,\r\n booleanAttribute,\r\n input,\r\n} from '@angular/core';\r\n\r\nexport type StackDirection = 'row' | 'column';\r\nexport type StackGap = 'none' | 'sm' | 'md' | 'lg';\r\nexport type StackAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';\r\nexport type StackJustify = 'start' | 'center' | 'end' | 'between' | 'around';\r\n\r\n/**\r\n * SkraStack — flex layout primitive that arranges its children (ADR 041).\r\n *\r\n * Component-on-native-host via the bounded allowlist below. As a child-arranger\r\n * it additionally accepts list hosts (`ul`/`ol`/`menu`) — their UA list styling\r\n * is normalized in stack.scss per the Host Normalization Contract. List markers\r\n * are restorable without `!important` via a direct `li {}` rule (a direct\r\n * declaration beats the inherited `list-style: none`).\r\n *\r\n * Stack consumes `@container`; it MUST NOT establish a container-query context\r\n * (ADR 016 §3.3, amended by ADR 041).\r\n */\r\n@Component({\r\n selector:\r\n 'div[skraStack], section[skraStack], article[skraStack], main[skraStack], nav[skraStack], aside[skraStack], header[skraStack], footer[skraStack], form[skraStack], figure[skraStack], ul[skraStack], ol[skraStack], menu[skraStack]',\r\n standalone: true,\r\n template: '<ng-content></ng-content>',\r\n styleUrl: './stack.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n host: {\r\n // CSP-Safe State Reflection — only data-attributes, no [style.*]. The\r\n // layout reset (margin/box-sizing) + list/UA normalization live in\r\n // stack.scss `:host` (ADR 022 / ADR 041 Host Normalization Contract);\r\n // host-metadata bare keys would render as junk attributes, not styles.\r\n '[attr.data-skra-direction]': 'direction()',\r\n '[attr.data-skra-gap]': 'gap()',\r\n '[attr.data-skra-align]': 'align()',\r\n '[attr.data-skra-justify]': 'justify()',\r\n '[attr.data-skra-wrap]': 'wrap() ? \"\" : null',\r\n },\r\n})\r\nexport class SkraStack {\r\n readonly direction = input<StackDirection>('column');\r\n readonly gap = input<StackGap>('md');\r\n readonly align = input<StackAlign>('stretch');\r\n readonly justify = input<StackJustify>('start');\r\n readonly wrap = input(false, { transform: booleanAttribute });\r\n}\r\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\nexport type GridColumns =\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | 'auto-fit'\n | 'auto-fill';\nexport type GridMin = 'sm' | 'md' | 'lg';\nexport type GridGap = 'none' | 'sm' | 'md' | 'lg';\nexport type GridFlow = 'row' | 'column' | 'dense';\nexport type GridAlign = 'start' | 'center' | 'end' | 'stretch';\n\n/**\n * SkraGrid — 2D layout primitive that arranges its children in a CSS Grid\n * (ADR 041, scope O-3). The bucket-only companion to Stack (1D) and Box (spacing).\n *\n * Component-on-native-host via the bounded allowlist below. As a child-arranger\n * it additionally accepts list hosts (`ul`/`ol`/`menu`) — their UA list styling\n * is normalized in grid.scss per the Host Normalization Contract.\n *\n * Bucket-only by design: every axis is an enumerated input reflected as a\n * `data-skra-*` attribute → token. Arbitrary track lists, `grid-template-areas`,\n * named lines and per-item placement are OUT of scope (bespoke = native CSS) —\n * exposing them would force an `[style.*]` host binding (ADR 021 violation).\n *\n * Responsiveness: `columns=\"auto-fit\"` (default) + `min` produce an intrinsically\n * responsive card grid via `repeat(auto-fit, minmax(var(--min), 1fr))` — column\n * count tracks available width with NO media/container queries. Grid consumes\n * `@container`; it MUST NOT establish a container-query context (ADR 041 §3.3).\n */\n@Component({\n selector:\n 'div[skraGrid], section[skraGrid], article[skraGrid], main[skraGrid], nav[skraGrid], aside[skraGrid], header[skraGrid], footer[skraGrid], form[skraGrid], figure[skraGrid], ul[skraGrid], ol[skraGrid], menu[skraGrid]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './grid.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection — only data-attributes, no [style.*]. The layout\n // reset (margin/box-sizing/display:grid) + list/UA normalization live in\n // grid.scss `:host` (ADR 022 / ADR 041 Host Normalization Contract);\n // host-metadata bare keys would render as junk attributes, not styles.\n '[attr.data-skra-columns]': 'columns()',\n '[attr.data-skra-min]': 'min()',\n '[attr.data-skra-gap]': 'gap()',\n '[attr.data-skra-flow]': 'flow()',\n '[attr.data-skra-align]': 'align()',\n '[attr.data-skra-justify-items]': 'justifyItems()',\n },\n})\nexport class SkraGrid {\n readonly columns = input<GridColumns>('auto-fit');\n readonly min = input<GridMin>('md');\n readonly gap = input<GridGap>('md');\n readonly flow = input<GridFlow>('row');\n readonly align = input<GridAlign>('stretch');\n readonly justifyItems = input<GridAlign>('stretch');\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n input,\n} from '@angular/core';\n\nexport type SurfacePadding = 'none' | 'sm' | 'md' | 'lg';\n\n/**\n * SkraSurface — the painted container primitive (ADR 0053), Box's painting sibling.\n *\n * Component-on-native-host: layers onto a generic flow/sectioning container via the\n * bounded host allowlist below — NEVER a `<skra-surface>` custom element, and never\n * an interactive host (ADR 0055 R3.1: `a[skraSurface]` / `button[skraSurface]` are\n * unsupported). \"Card\", \"Panel\" and \"Tile\" are consumer compositions of Surface +\n * `[skraStack]`/`[skraBox]` + a heading — not Skra components (ADR 0053 rule 1).\n *\n * **State-reading doctrine (ADR 0055).** Surface is a READER of state, never an\n * OWNER of state. It exposes no `interactive`/`hoverable`/`selected`/`elevation`\n * input and emits no `role`/`tabindex`/`aria-*`. Focus and selection are READ from\n * the markup the consumer authored (see surface.scss) — which is why an\n * informational card gets no interactive paint with zero API, and why a selected\n * card can never disagree with what assistive technology announces.\n *\n * Zero baked semantics: sectioning role and accessible name are 100% consumer-owned\n * (ADR 0053 rule 6). No `container-type` in v1 (ADR 0055 R7.1).\n */\n@Component({\n selector:\n 'div[skraSurface], section[skraSurface], article[skraSurface], main[skraSurface], nav[skraSurface], aside[skraSurface], header[skraSurface], footer[skraSurface], figure[skraSurface]',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './surface.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // CSP-Safe State Reflection (ADR 021) — NO inline styles. This is the ONLY\n // host attribute Surface emits (ADR 0055 R2): no role, no tabindex, no aria-*.\n '[attr.data-skra-padding]': 'padding()',\n },\n})\nexport class SkraSurface {\n /**\n * Semantic padding preset, reusing Box's enum for cross-atom consistency\n * (ADR 0053 rule 4). Defaults to `md` rather than Box's `none` — Box is\n * spacing-neutral by construction, a painted container is not (ADR 0055 R1).\n */\n readonly padding = input<SurfacePadding>('md');\n}\n","import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n ViewEncapsulation,\r\n afterNextRender,\r\n inject,\r\n} from '@angular/core';\r\n\r\ndeclare const ngDevMode: boolean | undefined;\r\n\r\n/**\r\n * SkraSurfaceLink — the stretched-link marker that makes a whole `SkraSurface`\r\n * one activation target (ADR 0055 R3/R4).\r\n *\r\n * Placed on the consumer's OWN primary control — `<a>` for navigation, `<button>`\r\n * where the destination is an action — never on the Surface host (R3.1:\r\n * `a[skraSurface]` / `button[skraSurface]` stay unsupported). Its own host\r\n * stylesheet stretches the control's hit area over the nearest Surface with a\r\n * `::after` overlay; the Surface reads `:has([skraSurfaceLink]…)` and paints hover\r\n * and the focus ring on itself, because the surface is the target (R4.2).\r\n *\r\n * Accessible name: the control's own visible text (ADR 033 Path 1) — normally the\r\n * card's heading. No inputs, no `aria-*`, no `role`, no `tabindex`: the native\r\n * host already is the one tab stop.\r\n *\r\n * Dev-mode console errors (R3.3): no `[skraSurface]` ancestor (the overlay would\r\n * stretch over whatever positioned ancestor happens to exist — possibly the whole\r\n * page), and more than one marker whose NEAREST Surface is the same (two overlays\r\n * = an invisible hit-area conflict). Checked on the rendered DOM, not through DI:\r\n * a consumer's own card component projecting content into `<article skraSurface>`\r\n * puts the marker inside the Surface in the DOM while its injector chain follows\r\n * the declaring template and never meets the Surface.\r\n */\r\n@Component({\r\n selector: 'a[skraSurfaceLink], button[skraSurfaceLink]',\r\n standalone: true,\r\n template: '<ng-content></ng-content>',\r\n styleUrl: './surface-link.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n})\r\nexport class SkraSurfaceLink {\r\n constructor() {\r\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\r\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\r\n // Browser-only (afterNextRender never runs on the server) and read-only: the\r\n // DOM is inspected, never mutated.\r\n afterNextRender(() => checkPlacement(host));\r\n }\r\n }\r\n}\r\n\r\n/** Markers already reported as duplicates, per Surface — so one conflict logs once. */\r\nconst reportedMarkers = new WeakMap<Element, WeakSet<Element>>();\r\n\r\n/** Dev-mode placement check (ADR 0055 R3 / R3.3). */\r\nfunction checkPlacement(host: HTMLElement): void {\r\n const surface = host.parentElement?.closest('[skraSurface]');\r\n if (!surface) {\r\n console.error(\r\n '[skraSurfaceLink] must sit inside a [skraSurface] host — its overlay stretches ' +\r\n 'over the nearest Surface, and without one it would cover whatever positioned ' +\r\n 'ancestor exists (ADR 0055 R3).',\r\n host,\r\n );\r\n return;\r\n }\r\n const own = Array.from(surface.querySelectorAll('[skraSurfaceLink]')).filter(\r\n (link) => link.parentElement?.closest('[skraSurface]') === surface,\r\n );\r\n // Reported once per conflict, by whichever marker renders into it — including one\r\n // inserted BEFORE an existing marker, which a \"not the first in DOM order\" rule\r\n // missed (PR #9 review, S2). A marker that has already been reported stays quiet.\r\n if (own.length > 1) {\r\n const seen = reportedMarkers.get(surface) ?? new WeakSet<Element>();\r\n reportedMarkers.set(surface, seen);\r\n if (own.every((link) => seen.has(link))) return;\r\n own.forEach((link) => seen.add(link));\r\n console.error(\r\n '[skraSurfaceLink] appears more than once in the same [skraSurface] — at most one ' +\r\n 'stretched link per surface (ADR 0055 R3.3). Make each target its own Surface, ' +\r\n 'or keep the secondary control a plain link/button raised above the overlay.',\r\n host,\r\n );\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { ViewEncapsulation, ChangeDetectionStrategy, Component, inject, ElementRef, afterNextRender } from '@angular/core';
3
3
 
4
4
  /**
5
5
  * SkraLink — text-style anchor primitive (ADR 032 §3.6).
@@ -21,9 +21,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImpor
21
21
  args: [{ selector: 'a[skraLink]', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, template: "<ng-content></ng-content>\n", styles: [":host{margin:0;color:var(--skra-comp-link-foreground);text-decoration-line:var(--skra-comp-link-text-decoration-line);text-decoration-thickness:var(--skra-comp-link-text-decoration-thickness);text-underline-offset:var(--skra-comp-link-text-underline-offset);transition:color var(--skra-comp-link-motion-duration) var(--skra-comp-link-motion-easing)}:host:visited{color:var(--skra-comp-link-foreground-visited)}@media(hover:hover)and (pointer:fine){:host:hover{color:var(--skra-comp-link-foreground-hover)}}:host:active{color:var(--skra-comp-link-foreground-active)}:host:focus-visible{outline-style:solid;outline-color:var(--skra-comp-link-focus-ring-color);outline-width:var(--skra-comp-link-focus-ring-width);outline-offset:2px}\n"] }]
22
22
  }] });
23
23
 
24
+ /**
25
+ * SkraNavLink — a navigation destination with a visible current state (ADR 0060).
26
+ *
27
+ * Selector: `a[skraNavLink]` — `<a>` is the sole host (R1: a nav item that does not
28
+ * navigate is not a nav item). A separate atom from `skraLink`: the surface shape and
29
+ * the current state belong to navigation chrome, `:visited` and the underline to prose.
30
+ *
31
+ * **A navigation link is a link.** It keeps every native `<a>` behaviour and owns no
32
+ * state: zero inputs, zero outputs, no host attributes. The current state is READ from
33
+ * the native `aria-current` the consumer or the router already sets
34
+ * (`routerLinkActive` + `ariaCurrentWhenActive="page"`) — the component never writes it,
35
+ * so the paint and what assistive technology announces cannot disagree (R3).
36
+ *
37
+ * No `intent`/`emphasis`/`size` (R2 — the guardrail that keeps the marketing CTA out,
38
+ * ADR 032 §3.5), no `disabled` (ADR 032 §3.6.3), no `ariaLabel` (ADR 033 Path 1: the
39
+ * visible text is the name).
40
+ *
41
+ * Dev-mode console error (R5.1): a nav link outside a navigation landmark (`<nav>` or
42
+ * `role="navigation"`). Checked on the rendered DOM, not through DI — a consumer
43
+ * component that projects the link into its own `<nav>` must not trip it.
44
+ */
45
+ class SkraNavLink {
46
+ constructor() {
47
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
48
+ const host = inject(ElementRef).nativeElement;
49
+ // Browser-only (afterNextRender never runs on the server) and read-only.
50
+ afterNextRender(() => {
51
+ if (!inNavigation(host)) {
52
+ console.error('[skraNavLink] must sit inside a navigation landmark (<nav> or ' +
53
+ 'role="navigation") — outside one, a pill-shaped link is the marketing CTA ' +
54
+ 'that ADR 032 §3.5 keeps unsupported (ADR 0060 R5.1).', host);
55
+ }
56
+ });
57
+ }
58
+ }
59
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraNavLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
60
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.1", type: SkraNavLink, isStandalone: true, selector: "a[skraNavLink]", ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{display:inline-flex;align-items:center;margin:0;box-sizing:border-box;background:var(--skra-comp-nav-link-background);color:var(--skra-comp-nav-link-foreground);border-color:var(--skra-comp-nav-link-border-color);border-width:var(--skra-comp-nav-link-border-width);border-style:var(--skra-comp-nav-link-border-style);border-radius:var(--skra-comp-nav-link-radius);box-shadow:var(--skra-comp-nav-link-shadow);padding-block:var(--skra-comp-nav-link-padding-block);padding-inline:var(--skra-comp-nav-link-padding-inline);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);text-decoration:none;transition:background-color var(--skra-comp-nav-link-motion-duration) var(--skra-comp-nav-link-motion-easing),color var(--skra-comp-nav-link-motion-duration) var(--skra-comp-nav-link-motion-easing),border-color var(--skra-comp-nav-link-motion-duration) var(--skra-comp-nav-link-motion-easing)}:host([aria-current]:not([aria-current=false]):not([aria-current=\"\"]):not([aria-current=undefined])){background:var(--skra-comp-nav-link-background-current);color:var(--skra-comp-nav-link-foreground-current);border-color:var(--skra-comp-nav-link-border-color-current);border-width:var(--skra-comp-nav-link-border-width-current);padding-block:calc(var(--skra-comp-nav-link-padding-block) - (var(--skra-comp-nav-link-border-width-current) - var(--skra-comp-nav-link-border-width)));padding-inline:calc(var(--skra-comp-nav-link-padding-inline) - (var(--skra-comp-nav-link-border-width-current) - var(--skra-comp-nav-link-border-width)))}@media(hover:hover)and (pointer:fine){:host(:hover:not([aria-current]:not([aria-current=false]):not([aria-current=\"\"]):not([aria-current=undefined]))){background:var(--skra-comp-nav-link-background-hover);color:var(--skra-comp-nav-link-foreground-hover)}}:host(:focus-visible){outline:var(--skra-comp-nav-link-focus-ring-width) solid var(--skra-comp-nav-link-focus-ring-color);outline-offset:var(--skra-comp-nav-link-focus-ring-width)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
61
+ }
62
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: SkraNavLink, decorators: [{
63
+ type: Component,
64
+ args: [{ selector: 'a[skraNavLink]', standalone: true, template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, styles: [":host{display:inline-flex;align-items:center;margin:0;box-sizing:border-box;background:var(--skra-comp-nav-link-background);color:var(--skra-comp-nav-link-foreground);border-color:var(--skra-comp-nav-link-border-color);border-width:var(--skra-comp-nav-link-border-width);border-style:var(--skra-comp-nav-link-border-style);border-radius:var(--skra-comp-nav-link-radius);box-shadow:var(--skra-comp-nav-link-shadow);padding-block:var(--skra-comp-nav-link-padding-block);padding-inline:var(--skra-comp-nav-link-padding-inline);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);text-decoration:none;transition:background-color var(--skra-comp-nav-link-motion-duration) var(--skra-comp-nav-link-motion-easing),color var(--skra-comp-nav-link-motion-duration) var(--skra-comp-nav-link-motion-easing),border-color var(--skra-comp-nav-link-motion-duration) var(--skra-comp-nav-link-motion-easing)}:host([aria-current]:not([aria-current=false]):not([aria-current=\"\"]):not([aria-current=undefined])){background:var(--skra-comp-nav-link-background-current);color:var(--skra-comp-nav-link-foreground-current);border-color:var(--skra-comp-nav-link-border-color-current);border-width:var(--skra-comp-nav-link-border-width-current);padding-block:calc(var(--skra-comp-nav-link-padding-block) - (var(--skra-comp-nav-link-border-width-current) - var(--skra-comp-nav-link-border-width)));padding-inline:calc(var(--skra-comp-nav-link-padding-inline) - (var(--skra-comp-nav-link-border-width-current) - var(--skra-comp-nav-link-border-width)))}@media(hover:hover)and (pointer:fine){:host(:hover:not([aria-current]:not([aria-current=false]):not([aria-current=\"\"]):not([aria-current=undefined]))){background:var(--skra-comp-nav-link-background-hover);color:var(--skra-comp-nav-link-foreground-hover)}}:host(:focus-visible){outline:var(--skra-comp-nav-link-focus-ring-width) solid var(--skra-comp-nav-link-focus-ring-color);outline-offset:var(--skra-comp-nav-link-focus-ring-width)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
65
+ }], ctorParameters: () => [] });
66
+ /**
67
+ * True when `el` sits inside a navigation landmark in the FLAT tree. `closest()` stops at a
68
+ * shadow root, but assistive technology does not: a link rendered in the shadow root of a
69
+ * consumer component that itself sits inside `<nav>` IS navigation. So the walk hops from
70
+ * a shadow root to its host and continues.
71
+ */
72
+ function inNavigation(el) {
73
+ let node = el;
74
+ while (node) {
75
+ const hit = node.closest('nav, [role="navigation"]');
76
+ if (hit)
77
+ return true;
78
+ const root = node.getRootNode();
79
+ node = root instanceof ShadowRoot ? root.host : null;
80
+ }
81
+ return false;
82
+ }
83
+
24
84
  /**
25
85
  * Generated bundle index. Do not edit.
26
86
  */
27
87
 
28
- export { SkraLink };
88
+ export { SkraLink, SkraNavLink };
29
89
  //# sourceMappingURL=skra-ui-link.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-link.mjs","sources":["../../../../libs/ui/link/src/lib/link/link.ts","../../../../libs/ui/link/src/lib/link/link.html","../../../../libs/ui/link/src/skra-ui-link.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n} from '@angular/core';\n\n/**\n * SkraLink — text-style anchor primitive (ADR 032 §3.6).\n *\n * Selector: `a[skraLink]` — `<a>` is the sole host.\n *\n * Per ADR 032 §3.6.3 there is no `disabled` input and no `data-skra-*`\n * Availability reflection: a link the user cannot or should not follow should\n * not be a link in the first place. Severity is deferred beyond MVP (§3.6.4).\n * Accessible Name follows ADR 033 Path 1 — visible text is the accessible name;\n * the component exposes no `ariaLabel` input.\n */\n@Component({\n selector: 'a[skraLink]',\n standalone: true,\n templateUrl: './link.html',\n styleUrl: './link.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n})\nexport class SkraLink {}\n","<ng-content></ng-content>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAMA;;;;;;;;;;AAUG;MASU,QAAQ,CAAA;uGAAR,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,uECzBrB,6BACA,EAAA,MAAA,EAAA,CAAA,8tBAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FDwBa,QAAQ,EAAA,UAAA,EAAA,CAAA;kBARpB,SAAS;+BACE,aAAa,EAAA,UAAA,EACX,IAAI,EAAA,eAAA,EAGC,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,QAAA,EAAA,6BAAA,EAAA,MAAA,EAAA,CAAA,8tBAAA,CAAA,EAAA;;;AEvB3C;;AAEG;;;;"}
1
+ {"version":3,"file":"skra-ui-link.mjs","sources":["../../../../libs/ui/link/src/lib/link/link.ts","../../../../libs/ui/link/src/lib/link/link.html","../../../../libs/ui/link/src/lib/nav-link/nav-link.ts","../../../../libs/ui/link/src/skra-ui-link.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n} from '@angular/core';\n\n/**\n * SkraLink — text-style anchor primitive (ADR 032 §3.6).\n *\n * Selector: `a[skraLink]` — `<a>` is the sole host.\n *\n * Per ADR 032 §3.6.3 there is no `disabled` input and no `data-skra-*`\n * Availability reflection: a link the user cannot or should not follow should\n * not be a link in the first place. Severity is deferred beyond MVP (§3.6.4).\n * Accessible Name follows ADR 033 Path 1 — visible text is the accessible name;\n * the component exposes no `ariaLabel` input.\n */\n@Component({\n selector: 'a[skraLink]',\n standalone: true,\n templateUrl: './link.html',\n styleUrl: './link.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n})\nexport class SkraLink {}\n","<ng-content></ng-content>\n","import {\r\n ChangeDetectionStrategy,\r\n Component,\r\n ElementRef,\r\n ViewEncapsulation,\r\n afterNextRender,\r\n inject,\r\n} from '@angular/core';\r\n\r\ndeclare const ngDevMode: boolean | undefined;\r\n\r\n/**\r\n * SkraNavLink — a navigation destination with a visible current state (ADR 0060).\r\n *\r\n * Selector: `a[skraNavLink]` — `<a>` is the sole host (R1: a nav item that does not\r\n * navigate is not a nav item). A separate atom from `skraLink`: the surface shape and\r\n * the current state belong to navigation chrome, `:visited` and the underline to prose.\r\n *\r\n * **A navigation link is a link.** It keeps every native `<a>` behaviour and owns no\r\n * state: zero inputs, zero outputs, no host attributes. The current state is READ from\r\n * the native `aria-current` the consumer or the router already sets\r\n * (`routerLinkActive` + `ariaCurrentWhenActive=\"page\"`) — the component never writes it,\r\n * so the paint and what assistive technology announces cannot disagree (R3).\r\n *\r\n * No `intent`/`emphasis`/`size` (R2 — the guardrail that keeps the marketing CTA out,\r\n * ADR 032 §3.5), no `disabled` (ADR 032 §3.6.3), no `ariaLabel` (ADR 033 Path 1: the\r\n * visible text is the name).\r\n *\r\n * Dev-mode console error (R5.1): a nav link outside a navigation landmark (`<nav>` or\r\n * `role=\"navigation\"`). Checked on the rendered DOM, not through DI — a consumer\r\n * component that projects the link into its own `<nav>` must not trip it.\r\n */\r\n@Component({\r\n selector: 'a[skraNavLink]',\r\n standalone: true,\r\n template: '<ng-content></ng-content>',\r\n styleUrl: './nav-link.scss',\r\n changeDetection: ChangeDetectionStrategy.OnPush,\r\n encapsulation: ViewEncapsulation.Emulated,\r\n})\r\nexport class SkraNavLink {\r\n constructor() {\r\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\r\n const host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\r\n // Browser-only (afterNextRender never runs on the server) and read-only.\r\n afterNextRender(() => {\r\n if (!inNavigation(host)) {\r\n console.error(\r\n '[skraNavLink] must sit inside a navigation landmark (<nav> or ' +\r\n 'role=\"navigation\") — outside one, a pill-shaped link is the marketing CTA ' +\r\n 'that ADR 032 §3.5 keeps unsupported (ADR 0060 R5.1).',\r\n host,\r\n );\r\n }\r\n });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * True when `el` sits inside a navigation landmark in the FLAT tree. `closest()` stops at a\r\n * shadow root, but assistive technology does not: a link rendered in the shadow root of a\r\n * consumer component that itself sits inside `<nav>` IS navigation. So the walk hops from\r\n * a shadow root to its host and continues.\r\n */\r\nfunction inNavigation(el: Element): boolean {\r\n let node: Element | null = el;\r\n while (node) {\r\n const hit = node.closest('nav, [role=\"navigation\"]');\r\n if (hit) return true;\r\n const root = node.getRootNode();\r\n node = root instanceof ShadowRoot ? root.host : null;\r\n }\r\n return false;\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAMA;;;;;;;;;;AAUG;MASU,QAAQ,CAAA;uGAAR,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,uECzBrB,6BACA,EAAA,MAAA,EAAA,CAAA,8tBAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FDwBa,QAAQ,EAAA,UAAA,EAAA,CAAA;kBARpB,SAAS;+BACE,aAAa,EAAA,UAAA,EACX,IAAI,EAAA,eAAA,EAGC,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,QAAA,EAAA,6BAAA,EAAA,MAAA,EAAA,CAAA,8tBAAA,CAAA,EAAA;;;AEZ3C;;;;;;;;;;;;;;;;;;;;AAoBG;MASU,WAAW,CAAA;AACtB,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,EAAE;YACjD,MAAM,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;;YAEtE,eAAe,CAAC,MAAK;AACnB,gBAAA,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;oBACvB,OAAO,CAAC,KAAK,CACX,gEAAgE;wBAC9D,4EAA4E;wBAC5E,sDAAsD,EACxD,IAAI,CACL;gBACH;AACF,YAAA,CAAC,CAAC;QACJ;IACF;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,0EALZ,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,8wEAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAK1B,WAAW,EAAA,UAAA,EAAA,CAAA;kBARvB,SAAS;+BACE,gBAAgB,EAAA,UAAA,EACd,IAAI,EAAA,QAAA,EACN,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,MAAA,EAAA,CAAA,8wEAAA,CAAA,EAAA;;AAqB3C;;;;;AAKG;AACH,SAAS,YAAY,CAAC,EAAW,EAAA;IAC/B,IAAI,IAAI,GAAmB,EAAE;IAC7B,OAAO,IAAI,EAAE;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC;AACpD,QAAA,IAAI,GAAG;AAAE,YAAA,OAAO,IAAI;AACpB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE;AAC/B,QAAA,IAAI,GAAG,IAAI,YAAY,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI;IACtD;AACA,IAAA,OAAO,KAAK;AACd;;AC1EA;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skra/ui",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,7 +10,7 @@
10
10
  "@angular/core": ">=22.0.0 <23.0.0",
11
11
  "@angular/common": ">=22.0.0 <23.0.0",
12
12
  "@angular/cdk": ">=22.0.0 <23.0.0",
13
- "@skra/tokens": "^0.8.0"
13
+ "@skra/tokens": "^0.9.0"
14
14
  },
15
15
  "dependencies": {
16
16
  "@floating-ui/dom": "^1.7.6",
@@ -23,7 +23,7 @@ declare class SkraBox {
23
23
 
24
24
  type StackDirection = 'row' | 'column';
25
25
  type StackGap = 'none' | 'sm' | 'md' | 'lg';
26
- type StackAlign = 'start' | 'center' | 'end' | 'stretch';
26
+ type StackAlign = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
27
27
  type StackJustify = 'start' | 'center' | 'end' | 'between' | 'around';
28
28
  /**
29
29
  * SkraStack — flex layout primitive that arranges its children (ADR 041).
@@ -112,6 +112,35 @@ declare class SkraSurface {
112
112
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SkraSurface, "div[skraSurface], section[skraSurface], article[skraSurface], main[skraSurface], nav[skraSurface], aside[skraSurface], header[skraSurface], footer[skraSurface], figure[skraSurface]", never, { "padding": { "alias": "padding"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
113
113
  }
114
114
 
115
- export { SkraBox, SkraGrid, SkraStack, SkraSurface };
115
+ /**
116
+ * SkraSurfaceLink — the stretched-link marker that makes a whole `SkraSurface`
117
+ * one activation target (ADR 0055 R3/R4).
118
+ *
119
+ * Placed on the consumer's OWN primary control — `<a>` for navigation, `<button>`
120
+ * where the destination is an action — never on the Surface host (R3.1:
121
+ * `a[skraSurface]` / `button[skraSurface]` stay unsupported). Its own host
122
+ * stylesheet stretches the control's hit area over the nearest Surface with a
123
+ * `::after` overlay; the Surface reads `:has([skraSurfaceLink]…)` and paints hover
124
+ * and the focus ring on itself, because the surface is the target (R4.2).
125
+ *
126
+ * Accessible name: the control's own visible text (ADR 033 Path 1) — normally the
127
+ * card's heading. No inputs, no `aria-*`, no `role`, no `tabindex`: the native
128
+ * host already is the one tab stop.
129
+ *
130
+ * Dev-mode console errors (R3.3): no `[skraSurface]` ancestor (the overlay would
131
+ * stretch over whatever positioned ancestor happens to exist — possibly the whole
132
+ * page), and more than one marker whose NEAREST Surface is the same (two overlays
133
+ * = an invisible hit-area conflict). Checked on the rendered DOM, not through DI:
134
+ * a consumer's own card component projecting content into `<article skraSurface>`
135
+ * puts the marker inside the Surface in the DOM while its injector chain follows
136
+ * the declaring template and never meets the Surface.
137
+ */
138
+ declare class SkraSurfaceLink {
139
+ constructor();
140
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SkraSurfaceLink, never>;
141
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SkraSurfaceLink, "a[skraSurfaceLink], button[skraSurfaceLink]", never, {}, {}, never, ["*"], true, never>;
142
+ }
143
+
144
+ export { SkraBox, SkraGrid, SkraStack, SkraSurface, SkraSurfaceLink };
116
145
  export type { BoxDisplay, BoxPadding, GridAlign, GridColumns, GridFlow, GridGap, GridMin, StackAlign, StackDirection, StackGap, StackJustify, SurfacePadding };
117
146
  //# sourceMappingURL=skra-ui-layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-layout.d.ts","sources":["../../../../libs/ui/layout/src/lib/box/box.ts","../../../../libs/ui/layout/src/lib/stack/stack.ts","../../../../libs/ui/layout/src/lib/grid/grid.ts","../../../../libs/ui/layout/src/lib/surface/surface.ts"],"mappings":";;AAOM,KAAM,UAAU;AAChB,KAAM,UAAU;AAEtB;;;;;;;;;;;AAWG;AACH,cAiBa,OAAO;sBACF,aAAA,CAAA,WAAA,CAAA,UAAA;sBACA,aAAA,CAAA,WAAA,CAAA,UAAA;oDAFL,OAAO;sDAAP,OAAO;AAGnB;;KClCW,cAAc;AACpB,KAAM,QAAQ;AACd,KAAM,UAAU;AAChB,KAAM,YAAY;AAExB;;;;;;;;;;;AAWG;AACH,cAoBa,SAAS;wBACF,aAAA,CAAA,WAAA,CAAA,cAAA;kBACN,aAAA,CAAA,WAAA,CAAA,QAAA;oBACE,aAAA,CAAA,WAAA,CAAA,UAAA;sBACE,aAAA,CAAA,WAAA,CAAA,YAAA;mBACH,aAAA,CAAA,wBAAA;oDALF,SAAS;sDAAT,SAAS;AAMrB;;KC5CW,WAAW;AASjB,KAAM,OAAO;AACb,KAAM,OAAO;AACb,KAAM,QAAQ;AACd,KAAM,SAAS;AAErB;;;;;;;;;;;;;;;;;AAiBG;AACH,cAqBa,QAAQ;sBACH,aAAA,CAAA,WAAA,CAAA,WAAA;kBACJ,aAAA,CAAA,WAAA,CAAA,OAAA;kBACA,aAAA,CAAA,WAAA,CAAA,OAAA;mBACC,aAAA,CAAA,WAAA,CAAA,QAAA;oBACC,aAAA,CAAA,WAAA,CAAA,SAAA;2BACO,aAAA,CAAA,WAAA,CAAA,SAAA;oDANV,QAAQ;sDAAR,QAAQ;AAOpB;;AC5DK,KAAM,cAAc;AAE1B;;;;;;;;;;;;;;;;;;AAkBG;AACH,cAca,WAAW;AACtB;;;;AAIG;sBACa,aAAA,CAAA,WAAA,CAAA,cAAA;oDANL,WAAW;sDAAX,WAAW;AAOvB;;;;","names":[]}
1
+ {"version":3,"file":"skra-ui-layout.d.ts","sources":["../../../../libs/ui/layout/src/lib/box/box.ts","../../../../libs/ui/layout/src/lib/stack/stack.ts","../../../../libs/ui/layout/src/lib/grid/grid.ts","../../../../libs/ui/layout/src/lib/surface/surface.ts","../../../../libs/ui/layout/src/lib/surface/surface-link.ts"],"mappings":";;AAOM,KAAM,UAAU;AAChB,KAAM,UAAU;AAEtB;;;;;;;;;;;AAWG;AACH,cAiBa,OAAO;sBACF,aAAA,CAAA,WAAA,CAAA,UAAA;sBACA,aAAA,CAAA,WAAA,CAAA,UAAA;oDAFL,OAAO;sDAAP,OAAO;AAGnB;;KClCW,cAAc;AACpB,KAAM,QAAQ;AACd,KAAM,UAAU;AAChB,KAAM,YAAY;AAExB;;;;;;;;;;;AAWG;AACH,cAoBa,SAAS;wBACF,aAAA,CAAA,WAAA,CAAA,cAAA;kBACN,aAAA,CAAA,WAAA,CAAA,QAAA;oBACE,aAAA,CAAA,WAAA,CAAA,UAAA;sBACE,aAAA,CAAA,WAAA,CAAA,YAAA;mBACH,aAAA,CAAA,wBAAA;oDALF,SAAS;sDAAT,SAAS;AAMrB;;KC5CW,WAAW;AASjB,KAAM,OAAO;AACb,KAAM,OAAO;AACb,KAAM,QAAQ;AACd,KAAM,SAAS;AAErB;;;;;;;;;;;;;;;;;AAiBG;AACH,cAqBa,QAAQ;sBACH,aAAA,CAAA,WAAA,CAAA,WAAA;kBACJ,aAAA,CAAA,WAAA,CAAA,OAAA;kBACA,aAAA,CAAA,WAAA,CAAA,OAAA;mBACC,aAAA,CAAA,WAAA,CAAA,QAAA;oBACC,aAAA,CAAA,WAAA,CAAA,SAAA;2BACO,aAAA,CAAA,WAAA,CAAA,SAAA;oDANV,QAAQ;sDAAR,QAAQ;AAOpB;;AC5DK,KAAM,cAAc;AAE1B;;;;;;;;;;;;;;;;;;AAkBG;AACH,cAca,WAAW;AACtB;;;;AAIG;sBACa,aAAA,CAAA,WAAA,CAAA,cAAA;oDANL,WAAW;sDAAX,WAAW;AAOvB;;ACtCD;;;;;;;;;;;;;;;;;;;;;;AAsBG;AACH,cAQa,eAAe;;oDAAf,eAAe;sDAAf,eAAe;AAS3B;;;;","names":[]}
@@ -16,5 +16,32 @@ declare class SkraLink {
16
16
  static ɵcmp: i0.ɵɵComponentDeclaration<SkraLink, "a[skraLink]", never, {}, {}, never, ["*"], true, never>;
17
17
  }
18
18
 
19
- export { SkraLink };
19
+ /**
20
+ * SkraNavLink — a navigation destination with a visible current state (ADR 0060).
21
+ *
22
+ * Selector: `a[skraNavLink]` — `<a>` is the sole host (R1: a nav item that does not
23
+ * navigate is not a nav item). A separate atom from `skraLink`: the surface shape and
24
+ * the current state belong to navigation chrome, `:visited` and the underline to prose.
25
+ *
26
+ * **A navigation link is a link.** It keeps every native `<a>` behaviour and owns no
27
+ * state: zero inputs, zero outputs, no host attributes. The current state is READ from
28
+ * the native `aria-current` the consumer or the router already sets
29
+ * (`routerLinkActive` + `ariaCurrentWhenActive="page"`) — the component never writes it,
30
+ * so the paint and what assistive technology announces cannot disagree (R3).
31
+ *
32
+ * No `intent`/`emphasis`/`size` (R2 — the guardrail that keeps the marketing CTA out,
33
+ * ADR 032 §3.5), no `disabled` (ADR 032 §3.6.3), no `ariaLabel` (ADR 033 Path 1: the
34
+ * visible text is the name).
35
+ *
36
+ * Dev-mode console error (R5.1): a nav link outside a navigation landmark (`<nav>` or
37
+ * `role="navigation"`). Checked on the rendered DOM, not through DI — a consumer
38
+ * component that projects the link into its own `<nav>` must not trip it.
39
+ */
40
+ declare class SkraNavLink {
41
+ constructor();
42
+ static ɵfac: i0.ɵɵFactoryDeclaration<SkraNavLink, never>;
43
+ static ɵcmp: i0.ɵɵComponentDeclaration<SkraNavLink, "a[skraNavLink]", never, {}, {}, never, ["*"], true, never>;
44
+ }
45
+
46
+ export { SkraLink, SkraNavLink };
20
47
  //# sourceMappingURL=skra-ui-link.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"skra-ui-link.d.ts","sources":["../../../../libs/ui/link/src/lib/link/link.ts"],"mappings":";;AAMA;;;;;;;;;;AAUG;AACH,cAQa,QAAQ;yCAAR,QAAQ;2CAAR,QAAQ;AAAG;;;;","names":[]}
1
+ {"version":3,"file":"skra-ui-link.d.ts","sources":["../../../../libs/ui/link/src/lib/link/link.ts","../../../../libs/ui/link/src/lib/nav-link/nav-link.ts"],"mappings":";;AAMA;;;;;;;;;;AAUG;AACH,cAQa,QAAQ;yCAAR,QAAQ;2CAAR,QAAQ;AAAG;;ACdxB;;;;;;;;;;;;;;;;;;;;AAoBG;AACH,cAQa,WAAW;;yCAAX,WAAW;2CAAX,WAAW;AAiBvB;;;;","names":[]}