@skra/ui 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +7 -7
  3. package/README.md +39 -39
  4. package/consumer-guide/README.md +84 -84
  5. package/consumer-guide/forms-and-binding.md +19 -3
  6. package/consumer-guide/layout.md +19 -0
  7. package/consumer-guide/overlays.md +100 -100
  8. package/consumer-guide/setup-and-gotchas.md +165 -103
  9. package/consumer-guide/theming-and-encapsulation.md +129 -115
  10. package/fesm2022/skra-ui-accordion.mjs +61 -43
  11. package/fesm2022/skra-ui-accordion.mjs.map +1 -1
  12. package/fesm2022/skra-ui-alert.mjs +26 -18
  13. package/fesm2022/skra-ui-alert.mjs.map +1 -1
  14. package/fesm2022/skra-ui-avatar.mjs +12 -8
  15. package/fesm2022/skra-ui-avatar.mjs.map +1 -1
  16. package/fesm2022/skra-ui-badge.mjs +19 -13
  17. package/fesm2022/skra-ui-badge.mjs.map +1 -1
  18. package/fesm2022/skra-ui-button.mjs +28 -19
  19. package/fesm2022/skra-ui-button.mjs.map +1 -1
  20. package/fesm2022/skra-ui-checkbox.mjs +3 -3
  21. package/fesm2022/skra-ui-checkbox.mjs.map +1 -1
  22. package/fesm2022/skra-ui-chip.mjs +15 -11
  23. package/fesm2022/skra-ui-chip.mjs.map +1 -1
  24. package/fesm2022/skra-ui-combobox.mjs +135 -130
  25. package/fesm2022/skra-ui-combobox.mjs.map +1 -1
  26. package/fesm2022/skra-ui-core.mjs +95 -73
  27. package/fesm2022/skra-ui-core.mjs.map +1 -1
  28. package/fesm2022/skra-ui-counter.mjs +9 -6
  29. package/fesm2022/skra-ui-counter.mjs.map +1 -1
  30. package/fesm2022/skra-ui-datepicker.mjs +334 -307
  31. package/fesm2022/skra-ui-datepicker.mjs.map +1 -1
  32. package/fesm2022/skra-ui-dialog.mjs +5 -4
  33. package/fesm2022/skra-ui-dialog.mjs.map +1 -1
  34. package/fesm2022/skra-ui-field.mjs +44 -34
  35. package/fesm2022/skra-ui-field.mjs.map +1 -1
  36. package/fesm2022/skra-ui-icon.mjs +5 -4
  37. package/fesm2022/skra-ui-icon.mjs.map +1 -1
  38. package/fesm2022/skra-ui-input.mjs +53 -12
  39. package/fesm2022/skra-ui-input.mjs.map +1 -1
  40. package/fesm2022/skra-ui-kbd.mjs +3 -3
  41. package/fesm2022/skra-ui-kbd.mjs.map +1 -1
  42. package/fesm2022/skra-ui-layout.mjs +33 -21
  43. package/fesm2022/skra-ui-layout.mjs.map +1 -1
  44. package/fesm2022/skra-ui-link.mjs +3 -3
  45. package/fesm2022/skra-ui-link.mjs.map +1 -1
  46. package/fesm2022/skra-ui-loading-indicator.mjs +3 -3
  47. package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -1
  48. package/fesm2022/skra-ui-menu.mjs +8 -7
  49. package/fesm2022/skra-ui-menu.mjs.map +1 -1
  50. package/fesm2022/skra-ui-multi-select.mjs +102 -98
  51. package/fesm2022/skra-ui-multi-select.mjs.map +1 -1
  52. package/fesm2022/skra-ui-native-select.mjs +35 -17
  53. package/fesm2022/skra-ui-native-select.mjs.map +1 -1
  54. package/fesm2022/skra-ui-popover.mjs +5 -4
  55. package/fesm2022/skra-ui-popover.mjs.map +1 -1
  56. package/fesm2022/skra-ui-progress.mjs +7 -5
  57. package/fesm2022/skra-ui-progress.mjs.map +1 -1
  58. package/fesm2022/skra-ui-radio.mjs +3 -3
  59. package/fesm2022/skra-ui-radio.mjs.map +1 -1
  60. package/fesm2022/skra-ui-rating-display.mjs +14 -9
  61. package/fesm2022/skra-ui-rating-display.mjs.map +1 -1
  62. package/fesm2022/skra-ui-select.mjs +98 -95
  63. package/fesm2022/skra-ui-select.mjs.map +1 -1
  64. package/fesm2022/skra-ui-skeleton.mjs +16 -13
  65. package/fesm2022/skra-ui-skeleton.mjs.map +1 -1
  66. package/fesm2022/skra-ui-star-rating.mjs +18 -11
  67. package/fesm2022/skra-ui-star-rating.mjs.map +1 -1
  68. package/fesm2022/skra-ui-status-dot.mjs +7 -5
  69. package/fesm2022/skra-ui-status-dot.mjs.map +1 -1
  70. package/fesm2022/skra-ui-switch.mjs +3 -3
  71. package/fesm2022/skra-ui-switch.mjs.map +1 -1
  72. package/fesm2022/skra-ui-tabs.mjs +43 -29
  73. package/fesm2022/skra-ui-tabs.mjs.map +1 -1
  74. package/fesm2022/skra-ui-textarea.mjs +41 -15
  75. package/fesm2022/skra-ui-textarea.mjs.map +1 -1
  76. package/fesm2022/skra-ui-toast.mjs +35 -23
  77. package/fesm2022/skra-ui-toast.mjs.map +1 -1
  78. package/fesm2022/skra-ui-tooltip.mjs +12 -9
  79. package/fesm2022/skra-ui-tooltip.mjs.map +1 -1
  80. package/fesm2022/skra-ui-typography.mjs +9 -6
  81. package/fesm2022/skra-ui-typography.mjs.map +1 -1
  82. package/package.json +5 -5
  83. package/types/skra-ui-input.d.ts +24 -1
  84. package/types/skra-ui-input.d.ts.map +1 -1
  85. package/types/skra-ui-native-select.d.ts +9 -1
  86. package/types/skra-ui-native-select.d.ts.map +1 -1
  87. package/types/skra-ui-textarea.d.ts +10 -1
  88. package/types/skra-ui-textarea.d.ts.map +1 -1
@@ -1,84 +1,84 @@
1
- # Consuming Skra — Agent Guide (Core)
2
-
3
- > **DRAFT (2026-06-25).** All 7 sections drafted. Snippets are grounded in source (verified
4
- > against `libs/ui/*`) + the `apps/dogfood` friction journal (`F-n`) + the Iron Boundaries.
5
- > Still draft: not yet referenced from a real consumer repo, not lint-gated against ADR drift,
6
- > and cross-engine/Safari caveats inherit Skra's own open item O-9.
7
-
8
- This guide is for an **agent working inside a repository that consumes Skra** (imports
9
- `@skra/*`), not for authoring Skra itself. It is written to be **copied into / referenced
10
- from a consumer repo**.
11
-
12
- ---
13
-
14
- ## How to use this guide (read this first — it is the point)
15
-
16
- This file is **tiered on purpose** (same model as `FORGE_ADR_SUMMARY.md`). To keep token
17
- cost low:
18
-
19
- 1. **Always load this core** (it is small) — the Iron Rules + the Section Router below.
20
- 2. Then load **at most the ONE section** that matches your current task (the Router maps
21
- task → file). Do **not** read every section "to be safe" — that defeats the design.
22
- 3. Open the full Skra ADR / spec only when a section explicitly points you there.
23
-
24
- ---
25
-
26
- ## Iron Rules of Consumption (the 6 a consumer breaks on day one)
27
-
28
- Violating these is a review blocker in a consumer repo just as inside Skra.
29
-
30
- 1. **Import only from public `@skra/ui/*` entry points** — never a deep relative `libs/...`
31
- path. Skra ships as one package, `@skra/ui`, with a secondary entry point per component
32
- (e.g. `@skra/ui/button`, `@skra/ui/typography`, `@skra/ui/select`); the primary
33
- `@skra/ui` exports nothing. (Full entry-point list → `setup-and-gotchas.md`.)
34
- 2. **You own the value.** Skra form atoms do **not** implement `ControlValueAccessor` /
35
- `FormValueControl` (ADR 037). Native-host atoms take `[formField]`/`[formControl]`/
36
- `[(ngModel)]`; value-owned overlays need an explicit `[value]`/`(valueChange)` bridge.
37
- (How → `forms-and-binding.md`.)
38
- 3. **Accessible name has exactly ONE path** (ADR 033). No Skra atom exposes an `ariaLabel`
39
- input; never put `aria-label` next to visible text on the same host. (Paths →
40
- `accessible-names.md`.)
41
- 4. **Inside `<skra-field>`, do not pass `id` / `for` / `aria-describedby` by hand** — the
42
- DI context wires label↔control↔hint↔error automatically (ADR 013).
43
- 5. **Under Signal Forms, native constraint attributes go in the schema, not the template.**
44
- `name` on grouped radios and `maxlength` / `min` / `pattern` on a `[formField]` host raise
45
- `NG8022` — Signal Forms owns them (F-1/F-2). (Details → `forms-and-binding.md`.)
46
- 6. **Never theme with inline styles.** No `[style.--skra-*]` bindings or `style="…"` (breaks
47
- CSP, ADR 021). Override tokens via a CSS class / `data-skra-*` scope; components are
48
- `margin: 0` (ADR 022) — spacing comes from layout atoms, never a margin on the component.
49
-
50
- ---
51
-
52
- ## Section Router — load the ONE that matches your task
53
-
54
- | Section | File | Load when | Status |
55
- | --------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------- | -------- |
56
- | **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls | ✅ draft |
57
- | **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel` | ✅ draft |
58
- | **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only` | ✅ draft |
59
- | **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe | ✅ draft |
60
- | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
61
- | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Grid; the gap scale | ✅ draft |
62
- | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
63
-
64
- ---
65
-
66
- ## Quick reference — task → section
67
-
68
- | I want to… | Go to |
69
- | -------------------------------------------------------------------------- | ----------------------------------------- |
70
- | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
71
- | Bind a text input / textarea to a form | `forms-and-binding.md` |
72
- | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
73
- | Name an icon-only button | `accessible-names.md` |
74
- | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
75
- | Mount Skra inside a Web Component / microfrontend | `theming-and-encapsulation.md` |
76
- | Open a dialog or tooltip | `overlays.md` |
77
- | Space components apart | `layout.md` |
78
- | Wrap a Skra atom in a project-specific component | `wrapping-patterns.md` |
79
-
80
- ---
81
-
82
- _Source of truth for the rules: Skra `FORGE_ADR_SUMMARY.md` + `adr/decisions/`. Real
83
- consumption evidence: `apps/dogfood/FRICTION_JOURNAL.md`. This guide restates the consumer
84
- slice of those — on divergence, the ADRs win._
1
+ # Consuming Skra — Agent Guide (Core)
2
+
3
+ > **DRAFT (2026-06-25).** All 7 sections drafted. Snippets are grounded in source (verified
4
+ > against `libs/ui/*`) + the `apps/dogfood` friction journal (`F-n`) + the Iron Boundaries.
5
+ > Still draft: not yet referenced from a real consumer repo, not lint-gated against ADR drift,
6
+ > and cross-engine/Safari caveats inherit Skra's own open item O-9.
7
+
8
+ This guide is for an **agent working inside a repository that consumes Skra** (imports
9
+ `@skra/*`), not for authoring Skra itself. It is written to be **copied into / referenced
10
+ from a consumer repo**.
11
+
12
+ ---
13
+
14
+ ## How to use this guide (read this first — it is the point)
15
+
16
+ This file is **tiered on purpose** (same model as `FORGE_ADR_SUMMARY.md`). To keep token
17
+ cost low:
18
+
19
+ 1. **Always load this core** (it is small) — the Iron Rules + the Section Router below.
20
+ 2. Then load **at most the ONE section** that matches your current task (the Router maps
21
+ task → file). Do **not** read every section "to be safe" — that defeats the design.
22
+ 3. Open the full Skra ADR / spec only when a section explicitly points you there.
23
+
24
+ ---
25
+
26
+ ## Iron Rules of Consumption (the 6 a consumer breaks on day one)
27
+
28
+ Violating these is a review blocker in a consumer repo just as inside Skra.
29
+
30
+ 1. **Import only from public `@skra/ui/*` entry points** — never a deep relative `libs/...`
31
+ path. Skra ships as one package, `@skra/ui`, with a secondary entry point per component
32
+ (e.g. `@skra/ui/button`, `@skra/ui/typography`, `@skra/ui/select`); the primary
33
+ `@skra/ui` exports nothing. (Full entry-point list → `setup-and-gotchas.md`.)
34
+ 2. **You own the value.** Skra form atoms do **not** implement `ControlValueAccessor` /
35
+ `FormValueControl` (ADR 037). Native-host atoms take `[formField]`/`[formControl]`/
36
+ `[(ngModel)]`; value-owned overlays need an explicit `[value]`/`(valueChange)` bridge.
37
+ (How → `forms-and-binding.md`.)
38
+ 3. **Accessible name has exactly ONE path** (ADR 033). No Skra atom exposes an `ariaLabel`
39
+ input; never put `aria-label` next to visible text on the same host. (Paths →
40
+ `accessible-names.md`.)
41
+ 4. **Inside `<skra-field>`, do not pass `id` / `for` / `aria-describedby` by hand** — the
42
+ DI context wires label↔control↔hint↔error automatically (ADR 013).
43
+ 5. **Under Signal Forms, native constraint attributes go in the schema, not the template.**
44
+ `name` on grouped radios and `maxlength` / `min` / `pattern` on a `[formField]` host raise
45
+ `NG8022` — Signal Forms owns them (F-1/F-2). (Details → `forms-and-binding.md`.)
46
+ 6. **Never theme with inline styles.** No `[style.--skra-*]` bindings or `style="…"` (breaks
47
+ CSP, ADR 021). Override tokens via a CSS class / `data-skra-*` scope; components are
48
+ `margin: 0` (ADR 022) — spacing comes from layout atoms, never a margin on the component.
49
+
50
+ ---
51
+
52
+ ## Section Router — load the ONE that matches your task
53
+
54
+ | Section | File | Load when | Status |
55
+ | --------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------- | -------- |
56
+ | **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls | ✅ draft |
57
+ | **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel` | ✅ draft |
58
+ | **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only` | ✅ draft |
59
+ | **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe | ✅ draft |
60
+ | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
61
+ | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Grid; the gap scale | ✅ draft |
62
+ | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
63
+
64
+ ---
65
+
66
+ ## Quick reference — task → section
67
+
68
+ | I want to… | Go to |
69
+ | -------------------------------------------------------------------------- | ----------------------------------------- |
70
+ | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
71
+ | Bind a text input / textarea to a form | `forms-and-binding.md` |
72
+ | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
73
+ | Name an icon-only button | `accessible-names.md` |
74
+ | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
75
+ | Mount Skra inside a Web Component / microfrontend | `theming-and-encapsulation.md` |
76
+ | Open a dialog or tooltip | `overlays.md` |
77
+ | Space components apart | `layout.md` |
78
+ | Wrap a Skra atom in a project-specific component | `wrapping-patterns.md` |
79
+
80
+ ---
81
+
82
+ _Source of truth for the rules: Skra `FORGE_ADR_SUMMARY.md` + `adr/decisions/`. Real
83
+ consumption evidence: `apps/dogfood/FRICTION_JOURNAL.md`. This guide restates the consumer
84
+ slice of those — on divergence, the ADRs win._
@@ -22,15 +22,31 @@ directives attach to the same host, so `[formField]`, `[formControl]`/`formContr
22
22
 
23
23
  ```html
24
24
  <!-- text: Signal Forms / Reactive / ngModel — all valid -->
25
- <input skraInput [formField]="form().email" />
26
- <input skraInput [formControl]="emailCtrl" />
27
- <input skraInput [(ngModel)]="email" />
25
+ <skra-field>
26
+ <label skraLabelFor>Email</label>
27
+ <input skraInput [formField]="form().email" />
28
+ </skra-field>
29
+
30
+ <skra-field>
31
+ <label skraLabelFor>Email</label>
32
+ <input skraInput [formControl]="emailCtrl" />
33
+ </skra-field>
34
+
35
+ <skra-field>
36
+ <label skraLabelFor>Email</label>
37
+ <input skraInput [(ngModel)]="email" />
38
+ </skra-field>
28
39
 
29
40
  <!-- checkbox / switch: native checked + change (or any form directive) -->
30
41
  <input type="checkbox" skraCheckbox
31
42
  [checked]="accepted()" (change)="accepted.set($any($event.target).checked)" />
32
43
  ```
33
44
 
45
+ > The `<skra-field>` wrapper above is about the **label**, not the binding — the form
46
+ > directive attaches to the native host either way. A control that needs no visible label,
47
+ > hint or error can stand alone (`<label>Search <input skraInput /></label>`); see
48
+ > `setup-and-gotchas.md` Gotcha 5.
49
+
34
50
  **Radios group natively** — `<fieldset skraFieldset>` + `<legend skraLegend>`, with
35
51
  selection coordinated through the shared `name`:
36
52
 
@@ -15,6 +15,25 @@ a native host** (a bounded allowlist of `div`/`section`/`nav`/`ul`/…), **never
15
15
  <ul skraStack gap="sm">…</ul> <!-- list markers normalized, restorable -->
16
16
  ```
17
17
 
18
+ ## Sizing container-filling components (e.g. `skra-select`)
19
+
20
+ Layout isolation (ADR 022) means a component never imposes its own outer size either. Some
21
+ components are **`display: block` and fill their container's inline size** by design — the
22
+ form controls especially (`skra-select`, `skra-combobox`, `input[skraInput]`, …), because
23
+ their primary home is a full-width form field. A **standalone** one (a toolbar select, a
24
+ filter control) therefore needs the consumer to size it — give it a width-bearing wrapper:
25
+
26
+ ```html
27
+ <div style="inline-size: 16rem"><skra-select …>…</skra-select></div>
28
+ ```
29
+
30
+ **Gotcha:** dropped straight into a `display: flex` / `display: grid` row with no explicit
31
+ `flex` / `inline-size` / `min-inline-size`, a container-filling component collapses toward its
32
+ content width — and when it holds no value yet it can shrink to just its padding + icon (looks
33
+ "broken"). This is the isolation contract working as intended, not a component bug: size the
34
+ component (wrapper width, a `skraGrid` track, or a `flex` basis), don't expect an intrinsic
35
+ width.
36
+
18
37
  ## Stack — 1D flex (`[skraStack]`)
19
38
 
20
39
  Arranges children in a row or column.
@@ -1,100 +1,100 @@
1
- # Overlays
2
-
3
- > Load this for: opening a dialog, tooltip, popover, or menu in the native top layer, and the
4
- > shadow/iframe limits. Governed by ADR 0044 (native top-layer substrate — no CDK portal, no
5
- > z-index). For `select` / `combobox` / `datepicker` **value binding**, see `forms-and-binding.md`
6
- > (they are value-owned overlays).
7
-
8
- ## Shared model
9
-
10
- All Skra overlays render in the **browser top layer** via native platform APIs (`<dialog>` +
11
- the Popover API), not a `document.body` portal. Consequences for you:
12
-
13
- - **No z-index management.** Stacking is invocation-order; nesting is always correct.
14
- - **Escape, light-dismiss, `::backdrop`** are platform-owned (per overlay, below).
15
- - They open correctly **from inside a Shadow DOM root**; per-context limits at the bottom.
16
-
17
- ## Dialog (modal) — `dialog[skraDialog]`
18
-
19
- A Component on the native `<dialog>`, opened with the **native** `showModal()` and closed with
20
- `close()`. `role="dialog"`, `aria-modal`, Escape, the `::backdrop` scrim, scroll-lock and focus
21
- containment are native:
22
-
23
- ```html
24
- <button skraButton (click)="dlg.showModal()">Delete…</button>
25
-
26
- <dialog skraDialog #dlg aria-labelledby="confirm-title" size="sm">
27
- <h2 id="confirm-title">Delete item?</h2>
28
- <p>This can't be undone.</p>
29
- <button skraButton autofocus (click)="dlg.close()">Cancel</button>
30
- <button skraButton (click)="confirm(); dlg.close()">Delete</button>
31
- </dialog>
32
- ```
33
-
34
- - **Name it** with `aria-labelledby` → a projected heading (no `ariaLabel` input — ADR 033).
35
- - **Initial focus** is the consumer-placed native `autofocus` attribute.
36
- - `size` is `sm | md | lg` (ADR 035).
37
-
38
- ## Tooltip — `[skraTooltip]` on your own trigger
39
-
40
- A supplementary, non-interactive **description** shown on hover AND focus. It is **not** the
41
- accessible name — it merges into the trigger's `aria-describedby`:
42
-
43
- ```html
44
- <button skraButton [skraTooltip]="'Open settings'" aria-label="Settings">⚙</button>
45
- ```
46
-
47
- An icon-only trigger **still needs its own accessible name** (Path 3 — see
48
- `accessible-names.md`); the tooltip never substitutes for it.
49
-
50
- ## Popover (non-modal panel) — `skra-popover`
51
-
52
- A Component on the panel with projected interactive content. The trigger is **your own button**
53
- linked by the native `popovertarget`; toggle, light-dismiss, Escape, and stacking are
54
- platform-owned. **You MUST give the panel an `id`:**
55
-
56
- ```html
57
- <button skraButton popovertarget="account-menu">Account</button>
58
- <skra-popover id="account-menu" aria-labelledby="account-menu-title">
59
- <h3 id="account-menu-title">Signed in as…</h3>
60
- <!-- interactive content -->
61
- </skra-popover>
62
- ```
63
-
64
- Focus is **not** trapped/force-moved (non-modal). Name via `aria-labelledby` → projected
65
- heading.
66
-
67
- ## Menu (commands) — `skra-menu` + `button[skraMenuItem]`
68
-
69
- A WAI-ARIA APG menu-button: a homogeneous command set, arrow-key navigated, activating an item
70
- closes it. Trigger is your own button with `popovertarget` + `aria-haspopup="menu"`; **give the
71
- panel an `id`**. Focus moves **into** the menu on open:
72
-
73
- ```html
74
- <button skraButton popovertarget="row-actions" aria-haspopup="menu">Actions</button>
75
- <skra-menu id="row-actions">
76
- <button skraMenuItem (click)="edit()">Edit</button>
77
- <button skraMenuItem disabled (click)="archive()">Archive</button>
78
- <button skraMenuItem (click)="remove()">Delete</button>
79
- </skra-menu>
80
- ```
81
-
82
- - **Side effects on open/close:** bind the **native `(toggle)`** event — there is no Skra
83
- output (native-first).
84
- - A right-click context menu is **not** a menu trigger mode — use a visible button (SRP +
85
- WCAG 2.1.1).
86
-
87
- ## Selection overlays — value binding lives elsewhere
88
-
89
- `skra-select`, `skra-multi-select`, `skra-combobox`, `skra-datepicker` render their listbox
90
- in the top layer like the above, **but their value is consumer-owned** — bind with the
91
- `[value]`/`(valueChange)` bridge documented in `forms-and-binding.md` (class B). The combobox
92
- filters with consumer-owned data (ADR 0049): you own the filtered option list.
93
-
94
- ## Limits across boundaries (same as theming — recap)
95
-
96
- - Overlays **open** from inside a Shadow DOM root (top layer + positioning cross the boundary,
97
- F-10). But the global-slice pieces — `::backdrop` scrim and `html:has()` scroll-lock — need
98
- the bridge / don't pierce the boundary (F-6/F-7). See `theming-and-encapsulation.md`.
99
- - **iframe**: overlays are contained to the frame's own top layer — they cannot overlay the
100
- parent app (F-11).
1
+ # Overlays
2
+
3
+ > Load this for: opening a dialog, tooltip, popover, or menu in the native top layer, and the
4
+ > shadow/iframe limits. Governed by ADR 0044 (native top-layer substrate — no CDK portal, no
5
+ > z-index). For `select` / `combobox` / `datepicker` **value binding**, see `forms-and-binding.md`
6
+ > (they are value-owned overlays).
7
+
8
+ ## Shared model
9
+
10
+ All Skra overlays render in the **browser top layer** via native platform APIs (`<dialog>` +
11
+ the Popover API), not a `document.body` portal. Consequences for you:
12
+
13
+ - **No z-index management.** Stacking is invocation-order; nesting is always correct.
14
+ - **Escape, light-dismiss, `::backdrop`** are platform-owned (per overlay, below).
15
+ - They open correctly **from inside a Shadow DOM root**; per-context limits at the bottom.
16
+
17
+ ## Dialog (modal) — `dialog[skraDialog]`
18
+
19
+ A Component on the native `<dialog>`, opened with the **native** `showModal()` and closed with
20
+ `close()`. `role="dialog"`, `aria-modal`, Escape, the `::backdrop` scrim, scroll-lock and focus
21
+ containment are native:
22
+
23
+ ```html
24
+ <button skraButton (click)="dlg.showModal()">Delete…</button>
25
+
26
+ <dialog skraDialog #dlg aria-labelledby="confirm-title" size="sm">
27
+ <h2 id="confirm-title">Delete item?</h2>
28
+ <p>This can't be undone.</p>
29
+ <button skraButton autofocus (click)="dlg.close()">Cancel</button>
30
+ <button skraButton (click)="confirm(); dlg.close()">Delete</button>
31
+ </dialog>
32
+ ```
33
+
34
+ - **Name it** with `aria-labelledby` → a projected heading (no `ariaLabel` input — ADR 033).
35
+ - **Initial focus** is the consumer-placed native `autofocus` attribute.
36
+ - `size` is `sm | md | lg` (ADR 035).
37
+
38
+ ## Tooltip — `[skraTooltip]` on your own trigger
39
+
40
+ A supplementary, non-interactive **description** shown on hover AND focus. It is **not** the
41
+ accessible name — it merges into the trigger's `aria-describedby`:
42
+
43
+ ```html
44
+ <button skraButton [skraTooltip]="'Open settings'" aria-label="Settings">⚙</button>
45
+ ```
46
+
47
+ An icon-only trigger **still needs its own accessible name** (Path 3 — see
48
+ `accessible-names.md`); the tooltip never substitutes for it.
49
+
50
+ ## Popover (non-modal panel) — `skra-popover`
51
+
52
+ A Component on the panel with projected interactive content. The trigger is **your own button**
53
+ linked by the native `popovertarget`; toggle, light-dismiss, Escape, and stacking are
54
+ platform-owned. **You MUST give the panel an `id`:**
55
+
56
+ ```html
57
+ <button skraButton popovertarget="account-menu">Account</button>
58
+ <skra-popover id="account-menu" aria-labelledby="account-menu-title">
59
+ <h3 id="account-menu-title">Signed in as…</h3>
60
+ <!-- interactive content -->
61
+ </skra-popover>
62
+ ```
63
+
64
+ Focus is **not** trapped/force-moved (non-modal). Name via `aria-labelledby` → projected
65
+ heading.
66
+
67
+ ## Menu (commands) — `skra-menu` + `button[skraMenuItem]`
68
+
69
+ A WAI-ARIA APG menu-button: a homogeneous command set, arrow-key navigated, activating an item
70
+ closes it. Trigger is your own button with `popovertarget` + `aria-haspopup="menu"`; **give the
71
+ panel an `id`**. Focus moves **into** the menu on open:
72
+
73
+ ```html
74
+ <button skraButton popovertarget="row-actions" aria-haspopup="menu">Actions</button>
75
+ <skra-menu id="row-actions">
76
+ <button skraMenuItem (click)="edit()">Edit</button>
77
+ <button skraMenuItem disabled (click)="archive()">Archive</button>
78
+ <button skraMenuItem (click)="remove()">Delete</button>
79
+ </skra-menu>
80
+ ```
81
+
82
+ - **Side effects on open/close:** bind the **native `(toggle)`** event — there is no Skra
83
+ output (native-first).
84
+ - A right-click context menu is **not** a menu trigger mode — use a visible button (SRP +
85
+ WCAG 2.1.1).
86
+
87
+ ## Selection overlays — value binding lives elsewhere
88
+
89
+ `skra-select`, `skra-multi-select`, `skra-combobox`, `skra-datepicker` render their listbox
90
+ in the top layer like the above, **but their value is consumer-owned** — bind with the
91
+ `[value]`/`(valueChange)` bridge documented in `forms-and-binding.md` (class B). The combobox
92
+ filters with consumer-owned data (ADR 0049): you own the filtered option list.
93
+
94
+ ## Limits across boundaries (same as theming — recap)
95
+
96
+ - Overlays **open** from inside a Shadow DOM root (top layer + positioning cross the boundary,
97
+ F-10). But the global-slice pieces — `::backdrop` scrim and `html:has()` scroll-lock — need
98
+ the bridge / don't pierce the boundary (F-6/F-7). See `theming-and-encapsulation.md`.
99
+ - **iframe**: overlays are contained to the frame's own top layer — they cannot overlay the
100
+ parent app (F-11).