@skra/ui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +39 -0
- package/consumer-guide/README.md +84 -0
- package/consumer-guide/accessible-names.md +80 -0
- package/consumer-guide/forms-and-binding.md +149 -0
- package/consumer-guide/layout.md +86 -0
- package/consumer-guide/overlays.md +100 -0
- package/consumer-guide/setup-and-gotchas.md +103 -0
- package/consumer-guide/theming-and-encapsulation.md +115 -0
- package/consumer-guide/wrapping-patterns.md +90 -0
- package/fesm2022/skra-ui-accordion.mjs +333 -0
- package/fesm2022/skra-ui-accordion.mjs.map +1 -0
- package/fesm2022/skra-ui-alert.mjs +110 -0
- package/fesm2022/skra-ui-alert.mjs.map +1 -0
- package/fesm2022/skra-ui-avatar.mjs +83 -0
- package/fesm2022/skra-ui-avatar.mjs.map +1 -0
- package/fesm2022/skra-ui-badge.mjs +91 -0
- package/fesm2022/skra-ui-badge.mjs.map +1 -0
- package/fesm2022/skra-ui-button.mjs +67 -0
- package/fesm2022/skra-ui-button.mjs.map +1 -0
- package/fesm2022/skra-ui-checkbox.mjs +98 -0
- package/fesm2022/skra-ui-checkbox.mjs.map +1 -0
- package/fesm2022/skra-ui-chip.mjs +82 -0
- package/fesm2022/skra-ui-chip.mjs.map +1 -0
- package/fesm2022/skra-ui-combobox.mjs +335 -0
- package/fesm2022/skra-ui-combobox.mjs.map +1 -0
- package/fesm2022/skra-ui-core.mjs +529 -0
- package/fesm2022/skra-ui-core.mjs.map +1 -0
- package/fesm2022/skra-ui-counter.mjs +53 -0
- package/fesm2022/skra-ui-counter.mjs.map +1 -0
- package/fesm2022/skra-ui-datepicker.mjs +867 -0
- package/fesm2022/skra-ui-datepicker.mjs.map +1 -0
- package/fesm2022/skra-ui-dialog.mjs +133 -0
- package/fesm2022/skra-ui-dialog.mjs.map +1 -0
- package/fesm2022/skra-ui-field.mjs +293 -0
- package/fesm2022/skra-ui-field.mjs.map +1 -0
- package/fesm2022/skra-ui-icon.mjs +54 -0
- package/fesm2022/skra-ui-icon.mjs.map +1 -0
- package/fesm2022/skra-ui-input.mjs +42 -0
- package/fesm2022/skra-ui-input.mjs.map +1 -0
- package/fesm2022/skra-ui-kbd.mjs +44 -0
- package/fesm2022/skra-ui-kbd.mjs.map +1 -0
- package/fesm2022/skra-ui-layout.mjs +119 -0
- package/fesm2022/skra-ui-layout.mjs.map +1 -0
- package/fesm2022/skra-ui-link.mjs +29 -0
- package/fesm2022/skra-ui-link.mjs.map +1 -0
- package/fesm2022/skra-ui-loading-indicator.mjs +24 -0
- package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -0
- package/fesm2022/skra-ui-menu.mjs +334 -0
- package/fesm2022/skra-ui-menu.mjs.map +1 -0
- package/fesm2022/skra-ui-multi-select.mjs +179 -0
- package/fesm2022/skra-ui-multi-select.mjs.map +1 -0
- package/fesm2022/skra-ui-native-select.mjs +123 -0
- package/fesm2022/skra-ui-native-select.mjs.map +1 -0
- package/fesm2022/skra-ui-popover.mjs +158 -0
- package/fesm2022/skra-ui-popover.mjs.map +1 -0
- package/fesm2022/skra-ui-progress.mjs +57 -0
- package/fesm2022/skra-ui-progress.mjs.map +1 -0
- package/fesm2022/skra-ui-radio.mjs +92 -0
- package/fesm2022/skra-ui-radio.mjs.map +1 -0
- package/fesm2022/skra-ui-rating-display.mjs +81 -0
- package/fesm2022/skra-ui-rating-display.mjs.map +1 -0
- package/fesm2022/skra-ui-select.mjs +151 -0
- package/fesm2022/skra-ui-select.mjs.map +1 -0
- package/fesm2022/skra-ui-skeleton.mjs +65 -0
- package/fesm2022/skra-ui-skeleton.mjs.map +1 -0
- package/fesm2022/skra-ui-star-rating.mjs +110 -0
- package/fesm2022/skra-ui-star-rating.mjs.map +1 -0
- package/fesm2022/skra-ui-status-dot.mjs +61 -0
- package/fesm2022/skra-ui-status-dot.mjs.map +1 -0
- package/fesm2022/skra-ui-switch.mjs +94 -0
- package/fesm2022/skra-ui-switch.mjs.map +1 -0
- package/fesm2022/skra-ui-tabs.mjs +356 -0
- package/fesm2022/skra-ui-tabs.mjs.map +1 -0
- package/fesm2022/skra-ui-textarea.mjs +81 -0
- package/fesm2022/skra-ui-textarea.mjs.map +1 -0
- package/fesm2022/skra-ui-toast.mjs +208 -0
- package/fesm2022/skra-ui-toast.mjs.map +1 -0
- package/fesm2022/skra-ui-tooltip.mjs +252 -0
- package/fesm2022/skra-ui-tooltip.mjs.map +1 -0
- package/fesm2022/skra-ui-typography.mjs +35 -0
- package/fesm2022/skra-ui-typography.mjs.map +1 -0
- package/fesm2022/skra-ui.mjs +4 -0
- package/fesm2022/skra-ui.mjs.map +1 -0
- package/package.json +175 -0
- package/types/skra-ui-accordion.d.ts +196 -0
- package/types/skra-ui-accordion.d.ts.map +1 -0
- package/types/skra-ui-alert.d.ts +81 -0
- package/types/skra-ui-alert.d.ts.map +1 -0
- package/types/skra-ui-avatar.d.ts +71 -0
- package/types/skra-ui-avatar.d.ts.map +1 -0
- package/types/skra-ui-badge.d.ts +73 -0
- package/types/skra-ui-badge.d.ts.map +1 -0
- package/types/skra-ui-button.d.ts +45 -0
- package/types/skra-ui-button.d.ts.map +1 -0
- package/types/skra-ui-checkbox.d.ts +52 -0
- package/types/skra-ui-checkbox.d.ts.map +1 -0
- package/types/skra-ui-chip.d.ts +60 -0
- package/types/skra-ui-chip.d.ts.map +1 -0
- package/types/skra-ui-combobox.d.ts +95 -0
- package/types/skra-ui-combobox.d.ts.map +1 -0
- package/types/skra-ui-core.d.ts +314 -0
- package/types/skra-ui-core.d.ts.map +1 -0
- package/types/skra-ui-counter.d.ts +33 -0
- package/types/skra-ui-counter.d.ts.map +1 -0
- package/types/skra-ui-datepicker.d.ts +164 -0
- package/types/skra-ui-datepicker.d.ts.map +1 -0
- package/types/skra-ui-dialog.d.ts +59 -0
- package/types/skra-ui-dialog.d.ts.map +1 -0
- package/types/skra-ui-field.d.ts +181 -0
- package/types/skra-ui-field.d.ts.map +1 -0
- package/types/skra-ui-icon.d.ts +38 -0
- package/types/skra-ui-icon.d.ts.map +1 -0
- package/types/skra-ui-input.d.ts +22 -0
- package/types/skra-ui-input.d.ts.map +1 -0
- package/types/skra-ui-kbd.d.ts +33 -0
- package/types/skra-ui-kbd.d.ts.map +1 -0
- package/types/skra-ui-layout.d.ts +86 -0
- package/types/skra-ui-layout.d.ts.map +1 -0
- package/types/skra-ui-link.d.ts +20 -0
- package/types/skra-ui-link.d.ts.map +1 -0
- package/types/skra-ui-loading-indicator.d.ts +9 -0
- package/types/skra-ui-loading-indicator.d.ts.map +1 -0
- package/types/skra-ui-menu.d.ts +116 -0
- package/types/skra-ui-menu.d.ts.map +1 -0
- package/types/skra-ui-multi-select.d.ts +47 -0
- package/types/skra-ui-multi-select.d.ts.map +1 -0
- package/types/skra-ui-native-select.d.ts +74 -0
- package/types/skra-ui-native-select.d.ts.map +1 -0
- package/types/skra-ui-popover.d.ts +57 -0
- package/types/skra-ui-popover.d.ts.map +1 -0
- package/types/skra-ui-progress.d.ts +41 -0
- package/types/skra-ui-progress.d.ts.map +1 -0
- package/types/skra-ui-radio.d.ts +54 -0
- package/types/skra-ui-radio.d.ts.map +1 -0
- package/types/skra-ui-rating-display.d.ts +56 -0
- package/types/skra-ui-rating-display.d.ts.map +1 -0
- package/types/skra-ui-select.d.ts +43 -0
- package/types/skra-ui-select.d.ts.map +1 -0
- package/types/skra-ui-skeleton.d.ts +42 -0
- package/types/skra-ui-skeleton.d.ts.map +1 -0
- package/types/skra-ui-star-rating.d.ts +71 -0
- package/types/skra-ui-star-rating.d.ts.map +1 -0
- package/types/skra-ui-status-dot.d.ts +62 -0
- package/types/skra-ui-status-dot.d.ts.map +1 -0
- package/types/skra-ui-switch.d.ts +54 -0
- package/types/skra-ui-switch.d.ts.map +1 -0
- package/types/skra-ui-tabs.d.ts +191 -0
- package/types/skra-ui-tabs.d.ts.map +1 -0
- package/types/skra-ui-textarea.d.ts +55 -0
- package/types/skra-ui-textarea.d.ts.map +1 -0
- package/types/skra-ui-toast.d.ts +140 -0
- package/types/skra-ui-toast.d.ts.map +1 -0
- package/types/skra-ui-tooltip.d.ts +90 -0
- package/types/skra-ui-tooltip.d.ts.map +1 -0
- package/types/skra-ui-typography.d.ts +17 -0
- package/types/skra-ui-typography.d.ts.map +1 -0
- package/types/skra-ui.d.ts +3 -0
- package/types/skra-ui.d.ts.map +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ViewEncapsulation, ChangeDetectionStrategy, Component, Directive } from '@angular/core';
|
|
3
|
+
import { SkraFieldContext } from '@skra/ui/field';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `textarea[skraTextarea]` — multiline text atom. Per ADR 040 this is a
|
|
7
|
+
* Component-on-a-native-host carrying its own host-scoped CSS (`textarea.scss`)
|
|
8
|
+
* via Emulated encapsulation, replacing the ADR 038 slice branch.
|
|
9
|
+
*
|
|
10
|
+
* `<textarea>` is a content-bearing host (its value is its text content), so the
|
|
11
|
+
* template MUST be `<ng-content>` — `template: ''` would discard the consumer's
|
|
12
|
+
* initial text. See ADR 040 §2.
|
|
13
|
+
*
|
|
14
|
+
* Sibling atom to SkraInput — split per atom-granularity rule (memory
|
|
15
|
+
* `feedback_atom_granularity.md`): textarea has capabilities `<input>` does not
|
|
16
|
+
* (multi-line content, native `rows` / `cols`, autosize composition via the
|
|
17
|
+
* sibling `[skraAutoResize]` directive).
|
|
18
|
+
*
|
|
19
|
+
* <skra-field>
|
|
20
|
+
* <label skraLabelFor>Bio</label>
|
|
21
|
+
* <textarea skraTextarea skraAutoResize rows="3"></textarea>
|
|
22
|
+
* </skra-field>
|
|
23
|
+
*
|
|
24
|
+
* Native `rows` / `cols` remain the consumer-facing sizing primitives — Forge
|
|
25
|
+
* does not duplicate them as Angular inputs (boundary #9 native-attr-first).
|
|
26
|
+
*/
|
|
27
|
+
class SkraTextarea {
|
|
28
|
+
context = inject(SkraFieldContext);
|
|
29
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
30
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: SkraTextarea, isStandalone: true, selector: "textarea[skraTextarea]", host: { properties: { "id": "context.fieldId()", "attr.aria-describedby": "context.describedByIds()", "attr.aria-invalid": "context.hasError() ? \"true\" : null", "attr.aria-required": "context.isRequired() ? \"true\" : null", "attr.disabled": "context.isDisabled() ? \"\" : null", "attr.readonly": "context.isReadonly() ? \"\" : null" } }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, styles: [":host{background:var(--skra-comp-field-background);color:var(--skra-comp-field-foreground);border-color:var(--skra-comp-field-border-color);border-width:var(--skra-comp-field-border-width);border-style:var(--skra-comp-field-border-style);border-radius:var(--skra-comp-field-radius);padding-block:var(--skra-comp-field-padding-block);padding-inline:var(--skra-comp-field-padding-inline);box-shadow:var(--skra-comp-field-shadow);font:inherit;outline:none;transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}:host(:hover:not(:disabled):not([readonly])){background:var(--skra-comp-field-background-hover);border-color:var(--skra-comp-field-border-color-hover)}:host(:focus-visible){border-color:var(--skra-comp-field-focus-ring-color);box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([readonly]){cursor:default}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
31
|
+
}
|
|
32
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraTextarea, decorators: [{
|
|
33
|
+
type: Component,
|
|
34
|
+
args: [{ selector: 'textarea[skraTextarea]', standalone: true, template: '<ng-content></ng-content>', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
|
|
35
|
+
'[id]': 'context.fieldId()',
|
|
36
|
+
'[attr.aria-describedby]': 'context.describedByIds()',
|
|
37
|
+
'[attr.aria-invalid]': 'context.hasError() ? "true" : null',
|
|
38
|
+
'[attr.aria-required]': 'context.isRequired() ? "true" : null',
|
|
39
|
+
// Availability — native attrs only (native-attr-first, boundary #9).
|
|
40
|
+
// <textarea> supports native `readonly`/`disabled`; NO `aria-readonly`/
|
|
41
|
+
// `aria-disabled` duplicate. Readonly styling keys off `:read-only`.
|
|
42
|
+
'[attr.disabled]': 'context.isDisabled() ? "" : null',
|
|
43
|
+
'[attr.readonly]': 'context.isReadonly() ? "" : null',
|
|
44
|
+
}, styles: [":host{background:var(--skra-comp-field-background);color:var(--skra-comp-field-foreground);border-color:var(--skra-comp-field-border-color);border-width:var(--skra-comp-field-border-width);border-style:var(--skra-comp-field-border-style);border-radius:var(--skra-comp-field-radius);padding-block:var(--skra-comp-field-padding-block);padding-inline:var(--skra-comp-field-padding-inline);box-shadow:var(--skra-comp-field-shadow);font:inherit;outline:none;transition:background-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),border-color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),color var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing),box-shadow var(--skra-comp-field-motion-duration) var(--skra-comp-field-motion-easing)}:host(:hover:not(:disabled):not([readonly])){background:var(--skra-comp-field-background-hover);border-color:var(--skra-comp-field-border-color-hover)}:host(:focus-visible){border-color:var(--skra-comp-field-focus-ring-color);box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-focus-ring-color)}:host(:disabled){cursor:not-allowed;opacity:var(--skra-sys-opacity-disabled)}:host([readonly]){cursor:default}:host([aria-invalid=true]){border-color:var(--skra-comp-field-error-color)}:host([aria-invalid=true]:focus-visible){box-shadow:0 0 0 var(--skra-comp-field-focus-ring-width) var(--skra-comp-field-error-color)}@media(prefers-reduced-motion:reduce){:host{transition:none}}\n"] }]
|
|
45
|
+
}] });
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Marker directive enabling content-driven sizing on a native form control via
|
|
49
|
+
* the modern CSS `field-sizing: content` declaration shipped from the skin's
|
|
50
|
+
* global utilities layer (`tokens/skins/skra/utilities.scss`).
|
|
51
|
+
*
|
|
52
|
+
* <textarea skraTextarea skraAutoResize rows="2"></textarea>
|
|
53
|
+
* <input skraInput skraAutoResize type="text" />
|
|
54
|
+
*
|
|
55
|
+
* Composition-over-configuration: the host opts in by adding the attribute,
|
|
56
|
+
* not by toggling a boolean Angular input. The directive has no host bindings,
|
|
57
|
+
* no DI, and no template — its sole job is to make the selector available at
|
|
58
|
+
* Angular's compilation stage; CSS does the rest.
|
|
59
|
+
*
|
|
60
|
+
* Wide selector (textarea + input) reflects that `field-sizing: content` is a
|
|
61
|
+
* native CSS capability for both: textarea grows in block-size with rows,
|
|
62
|
+
* input grows in inline-size with content width.
|
|
63
|
+
*/
|
|
64
|
+
class SkraAutoResizeDirective {
|
|
65
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraAutoResizeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
66
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: SkraAutoResizeDirective, isStandalone: true, selector: "textarea[skraAutoResize], input[skraAutoResize]", ngImport: i0 });
|
|
67
|
+
}
|
|
68
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraAutoResizeDirective, decorators: [{
|
|
69
|
+
type: Directive,
|
|
70
|
+
args: [{
|
|
71
|
+
selector: 'textarea[skraAutoResize], input[skraAutoResize]',
|
|
72
|
+
standalone: true,
|
|
73
|
+
}]
|
|
74
|
+
}] });
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generated bundle index. Do not edit.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
export { SkraAutoResizeDirective, SkraTextarea };
|
|
81
|
+
//# sourceMappingURL=skra-ui-textarea.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skra-ui-textarea.mjs","sources":["../../../../libs/ui/textarea/src/lib/textarea/textarea.ts","../../../../libs/ui/textarea/src/lib/auto-resize/auto-resize.directive.ts","../../../../libs/ui/textarea/src/skra-ui-textarea.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n ViewEncapsulation,\n inject,\n} from '@angular/core';\nimport { SkraFieldContext } from '@skra/ui/field';\n\n/**\n * `textarea[skraTextarea]` — multiline text atom. Per ADR 040 this is a\n * Component-on-a-native-host carrying its own host-scoped CSS (`textarea.scss`)\n * via Emulated encapsulation, replacing the ADR 038 slice branch.\n *\n * `<textarea>` is a content-bearing host (its value is its text content), so the\n * template MUST be `<ng-content>` — `template: ''` would discard the consumer's\n * initial text. See ADR 040 §2.\n *\n * Sibling atom to SkraInput — split per atom-granularity rule (memory\n * `feedback_atom_granularity.md`): textarea has capabilities `<input>` does not\n * (multi-line content, native `rows` / `cols`, autosize composition via the\n * sibling `[skraAutoResize]` directive).\n *\n * <skra-field>\n * <label skraLabelFor>Bio</label>\n * <textarea skraTextarea skraAutoResize rows=\"3\"></textarea>\n * </skra-field>\n *\n * Native `rows` / `cols` remain the consumer-facing sizing primitives — Forge\n * does not duplicate them as Angular inputs (boundary #9 native-attr-first).\n */\n@Component({\n selector: 'textarea[skraTextarea]',\n standalone: true,\n // Content-bearing host — see ADR 040 §2.\n template: '<ng-content></ng-content>',\n styleUrl: './textarea.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n '[id]': 'context.fieldId()',\n '[attr.aria-describedby]': 'context.describedByIds()',\n '[attr.aria-invalid]': 'context.hasError() ? \"true\" : null',\n '[attr.aria-required]': 'context.isRequired() ? \"true\" : null',\n // Availability — native attrs only (native-attr-first, boundary #9).\n // <textarea> supports native `readonly`/`disabled`; NO `aria-readonly`/\n // `aria-disabled` duplicate. Readonly styling keys off `:read-only`.\n '[attr.disabled]': 'context.isDisabled() ? \"\" : null',\n '[attr.readonly]': 'context.isReadonly() ? \"\" : null',\n },\n})\nexport class SkraTextarea {\n readonly context = inject(SkraFieldContext);\n}\n","import { Directive } from '@angular/core';\n\n/**\n * Marker directive enabling content-driven sizing on a native form control via\n * the modern CSS `field-sizing: content` declaration shipped from the skin's\n * global utilities layer (`tokens/skins/skra/utilities.scss`).\n *\n * <textarea skraTextarea skraAutoResize rows=\"2\"></textarea>\n * <input skraInput skraAutoResize type=\"text\" />\n *\n * Composition-over-configuration: the host opts in by adding the attribute,\n * not by toggling a boolean Angular input. The directive has no host bindings,\n * no DI, and no template — its sole job is to make the selector available at\n * Angular's compilation stage; CSS does the rest.\n *\n * Wide selector (textarea + input) reflects that `field-sizing: content` is a\n * native CSS capability for both: textarea grows in block-size with rows,\n * input grows in inline-size with content width.\n */\n@Directive({\n selector: 'textarea[skraAutoResize], input[skraAutoResize]',\n standalone: true,\n})\nexport class SkraAutoResizeDirective {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAQA;;;;;;;;;;;;;;;;;;;;;AAqBG;MAqBU,YAAY,CAAA;AACd,IAAA,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC;uGADhC,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,uaAhBb,2BAA2B,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,i/CAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAgB1B,YAAY,EAAA,UAAA,EAAA,CAAA;kBApBxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,wBAAwB,EAAA,UAAA,EACtB,IAAI,EAAA,QAAA,EAEN,2BAA2B,EAAA,eAAA,EAEpB,uBAAuB,CAAC,MAAM,EAAA,aAAA,EAChC,iBAAiB,CAAC,QAAQ,EAAA,IAAA,EACnC;AACJ,wBAAA,MAAM,EAAE,mBAAmB;AAC3B,wBAAA,yBAAyB,EAAE,0BAA0B;AACrD,wBAAA,qBAAqB,EAAE,oCAAoC;AAC3D,wBAAA,sBAAsB,EAAE,sCAAsC;;;;AAI9D,wBAAA,iBAAiB,EAAE,kCAAkC;AACrD,wBAAA,iBAAiB,EAAE,kCAAkC;AACtD,qBAAA,EAAA,MAAA,EAAA,CAAA,i/CAAA,CAAA,EAAA;;;AC9CH;;;;;;;;;;;;;;;;AAgBG;MAKU,uBAAuB,CAAA;uGAAvB,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAvB,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iDAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAvB,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBAJnC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iDAAiD;AAC3D,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;ACtBD;;AAEG;;;;"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Directive, InjectionToken, input, booleanAttribute, output, contentChild, computed, viewChild, inject, afterNextRender, DestroyRef, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, contentChildren, afterRenderEffect } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Marker directive for the leading icon slot of `<skra-toast>`.
|
|
6
|
+
* Detected via `contentChild` (ADR 020 Smart Slots v2.0) to toggle the icon
|
|
7
|
+
* wrapper without emitting an empty DOM node. Feedback-local (not the button
|
|
8
|
+
* lib's `[skra-start]`) to avoid a cross-lib marker dependency — mirrors
|
|
9
|
+
* `[skra-alert-icon]` / `[skra-badge-icon]`.
|
|
10
|
+
*/
|
|
11
|
+
class SkraToastIcon {
|
|
12
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraToastIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
13
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: SkraToastIcon, isStandalone: true, selector: "[skra-toast-icon]", ngImport: i0 });
|
|
14
|
+
}
|
|
15
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraToastIcon, decorators: [{
|
|
16
|
+
type: Directive,
|
|
17
|
+
args: [{
|
|
18
|
+
selector: '[skra-toast-icon]',
|
|
19
|
+
standalone: true,
|
|
20
|
+
}]
|
|
21
|
+
}] });
|
|
22
|
+
|
|
23
|
+
const SKRA_TOAST_ANNOUNCER = new InjectionToken('SKRA_TOAST_ANNOUNCER');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `<skra-toast>` — an ephemeral, auto-dismissing feedback message rendered inside a
|
|
27
|
+
* `<skra-toast-region>` top-layer stack (Feedback/Status family, ADR 0052; Tier 2 overlay,
|
|
28
|
+
* ADR 0044). Visually an Alert (intent×emphasis seam, 4th consumer) with a timer.
|
|
29
|
+
*
|
|
30
|
+
* Announcement (ADR 0052 §7): this host carries NO `role`/`aria-live`. On appearance the
|
|
31
|
+
* toast pushes its message text into the region's persistent role-only announcer via the
|
|
32
|
+
* injected `SkraToastAnnouncer`, at the politeness of `live` (`off` → no announcement).
|
|
33
|
+
*
|
|
34
|
+
* Auto-dismiss (WCAG 2.2.1): after `duration` ms the toast emits `(dismissed)`. The timer
|
|
35
|
+
* PAUSES on hover and on focus-within and resumes with the remaining time; `duration=0`
|
|
36
|
+
* disables it (persistent — pair with `dismissible`). Forge does NOT self-remove — the
|
|
37
|
+
* consumer removes the model from its array on `(dismissed)` (agnostic doctrine; the Alert
|
|
38
|
+
* precedent). `(dismissed)` is the single removal channel (timer elapse OR close control).
|
|
39
|
+
*/
|
|
40
|
+
class SkraToast {
|
|
41
|
+
// --- Variant Surface (ADR 035) ---
|
|
42
|
+
intent = input('neutral', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
|
|
43
|
+
emphasis = input('medium', ...(ngDevMode ? [{ debugName: "emphasis" }] : /* istanbul ignore next */ []));
|
|
44
|
+
// --- Announcement (ADR 0052 §7) ---
|
|
45
|
+
live = input('polite', ...(ngDevMode ? [{ debugName: "live" }] : /* istanbul ignore next */ []));
|
|
46
|
+
// --- Auto-dismiss + dismissal ---
|
|
47
|
+
duration = input(5000, ...(ngDevMode ? [{ debugName: "duration" }] : /* istanbul ignore next */ []));
|
|
48
|
+
dismissible = input(false, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
49
|
+
dismissed = output();
|
|
50
|
+
// --- Smart Slots v2.0 (ADR 020) ---
|
|
51
|
+
iconSlot = contentChild(SkraToastIcon, ...(ngDevMode ? [{ debugName: "iconSlot" }] : /* istanbul ignore next */ []));
|
|
52
|
+
hasIcon = computed(() => !!this.iconSlot(), ...(ngDevMode ? [{ debugName: "hasIcon" }] : /* istanbul ignore next */ []));
|
|
53
|
+
/** The message column — its text is the announcement (excludes the icon + sr-only close label). */
|
|
54
|
+
contentRef = viewChild.required('content');
|
|
55
|
+
announcer = inject(SKRA_TOAST_ANNOUNCER, {
|
|
56
|
+
optional: true,
|
|
57
|
+
});
|
|
58
|
+
timerId = null;
|
|
59
|
+
remaining = 0;
|
|
60
|
+
startedAt = 0;
|
|
61
|
+
constructor() {
|
|
62
|
+
afterNextRender(() => {
|
|
63
|
+
// Announce on appearance (browser-only; projected content is present post-render).
|
|
64
|
+
const live = this.live();
|
|
65
|
+
if (live !== 'off' && this.announcer) {
|
|
66
|
+
const text = this.contentRef().nativeElement.textContent?.trim() ?? '';
|
|
67
|
+
if (text)
|
|
68
|
+
this.announcer.announce(text, live);
|
|
69
|
+
}
|
|
70
|
+
// Start the auto-dismiss countdown (timers are browser-only — SSR-safe by construction).
|
|
71
|
+
this.remaining = this.duration();
|
|
72
|
+
this.start();
|
|
73
|
+
});
|
|
74
|
+
inject(DestroyRef).onDestroy(() => this.clear());
|
|
75
|
+
}
|
|
76
|
+
onDismiss() {
|
|
77
|
+
this.clear();
|
|
78
|
+
this.dismissed.emit();
|
|
79
|
+
}
|
|
80
|
+
/** Pause the countdown, banking the remaining time (WCAG 2.2.1). */
|
|
81
|
+
pause() {
|
|
82
|
+
if (this.timerId === null)
|
|
83
|
+
return;
|
|
84
|
+
clearTimeout(this.timerId);
|
|
85
|
+
this.timerId = null;
|
|
86
|
+
this.remaining -= Date.now() - this.startedAt;
|
|
87
|
+
}
|
|
88
|
+
/** Resume the countdown with the banked remaining time. */
|
|
89
|
+
resume() {
|
|
90
|
+
if (this.timerId !== null)
|
|
91
|
+
return;
|
|
92
|
+
this.start();
|
|
93
|
+
}
|
|
94
|
+
start() {
|
|
95
|
+
if (this.remaining <= 0)
|
|
96
|
+
return; // duration=0 (or fully elapsed) → persistent
|
|
97
|
+
this.startedAt = Date.now();
|
|
98
|
+
this.timerId = setTimeout(() => {
|
|
99
|
+
this.timerId = null;
|
|
100
|
+
this.dismissed.emit();
|
|
101
|
+
}, this.remaining);
|
|
102
|
+
}
|
|
103
|
+
clear() {
|
|
104
|
+
if (this.timerId !== null) {
|
|
105
|
+
clearTimeout(this.timerId);
|
|
106
|
+
this.timerId = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraToast, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
110
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: SkraToast, isStandalone: true, selector: "skra-toast", inputs: { intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null }, emphasis: { classPropertyName: "emphasis", publicName: "emphasis", isSignal: true, isRequired: false, transformFunction: null }, live: { classPropertyName: "live", publicName: "live", isSignal: true, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed" }, host: { listeners: { "mouseenter": "pause()", "mouseleave": "resume()", "focusin": "pause()", "focusout": "resume()" }, properties: { "attr.data-skra-intent": "intent()", "attr.data-skra-emphasis": "emphasis()", "attr.data-skra-has-icon": "hasIcon() ? \"\" : null" } }, queries: [{ propertyName: "iconSlot", first: true, predicate: SkraToastIcon, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (hasIcon()) {\n<span class=\"skra-toast-icon\">\n <ng-content select=\"[skra-toast-icon]\"></ng-content>\n</span>\n}\n\n<div class=\"skra-toast-content\" #content>\n <ng-content></ng-content>\n</div>\n\n@if (dismissible()) {\n<button type=\"button\" class=\"skra-toast-dismiss\" (click)=\"onDismiss()\">\n <span class=\"skra-toast-dismiss-glyph\" aria-hidden=\"true\">×</span>\n <ng-content select=\"[skra-toast-close-label]\"></ng-content>\n</button>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;align-items:flex-start;box-sizing:border-box;margin:0;gap:var(--skra-comp-toast-icon-gap);font-family:var(--skra-sys-typography-role-body-medium-font-family);font-size:var(--skra-sys-typography-role-body-medium-font-size);font-weight:var(--skra-sys-typography-role-body-medium-font-weight);line-height:var(--skra-sys-typography-role-body-medium-line-height);letter-spacing:var(--skra-sys-typography-role-body-medium-letter-spacing);background:var(--skra-comp-toast-background);color:var(--skra-comp-toast-foreground);border-color:var(--skra-comp-toast-border-color);border-width:var(--skra-comp-toast-border-width);border-style:var(--skra-comp-toast-border-style);border-radius:var(--skra-comp-toast-radius);padding-block:var(--skra-comp-toast-padding-block);padding-inline:var(--skra-comp-toast-padding-inline);box-shadow:var(--skra-comp-toast-shadow);opacity:1;transition:opacity var(--skra-comp-toast-motion-duration) var(--skra-comp-toast-motion-easing)}@starting-style{:host{opacity:0}}:host([data-skra-emphasis=high]){--skra-comp-toast-border-width: 0}:host([data-skra-intent=neutral][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-neutral-high, var(--skra-sys-color-neutral-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-neutral-high, var(--skra-sys-color-neutral-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-neutral-high, transparent )}:host([data-skra-intent=neutral][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-neutral-medium, var(--skra-sys-color-neutral-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-neutral-medium, var(--skra-sys-color-neutral-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-neutral-medium, var(--skra-sys-color-neutral-border) )}:host([data-skra-intent=info][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-info-high, var(--skra-sys-color-info-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-info-high, var(--skra-sys-color-info-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-info-high, transparent )}:host([data-skra-intent=info][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-info-medium, var(--skra-sys-color-info-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-info-medium, var(--skra-sys-color-info-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-info-medium, var(--skra-sys-color-info-border) )}:host([data-skra-intent=success][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-success-high, var(--skra-sys-color-success-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-success-high, var(--skra-sys-color-success-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-success-high, transparent )}:host([data-skra-intent=success][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-success-medium, var(--skra-sys-color-success-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-success-medium, var(--skra-sys-color-success-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-success-medium, var(--skra-sys-color-success-border) )}:host([data-skra-intent=warning][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-warning-high, var(--skra-sys-color-warning-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-warning-high, var(--skra-sys-color-warning-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-warning-high, transparent )}:host([data-skra-intent=warning][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-warning-medium, var(--skra-sys-color-warning-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-warning-medium, var(--skra-sys-color-warning-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-warning-medium, var(--skra-sys-color-warning-border) )}:host([data-skra-intent=danger][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-danger-high, var(--skra-sys-color-danger-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-danger-high, var(--skra-sys-color-danger-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-danger-high, transparent )}:host([data-skra-intent=danger][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-danger-medium, var(--skra-sys-color-danger-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-danger-medium, var(--skra-sys-color-danger-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-danger-medium, var(--skra-sys-color-danger-border) )}.skra-toast-icon{display:inline-flex;flex-shrink:0;align-items:center;block-size:1lh;--skra-comp-icon-size: var(--skra-comp-toast-icon-size)}.skra-toast-content{flex:1 1 auto;min-inline-size:0}.skra-toast-dismiss{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;inline-size:var(--skra-comp-toast-dismiss-size);block-size:var(--skra-comp-toast-dismiss-size);margin:0;padding:0;border:0;background:transparent;color:inherit;cursor:pointer;border-radius:var(--skra-comp-toast-radius);transition:opacity var(--skra-comp-toast-motion-duration) var(--skra-comp-toast-motion-easing)}.skra-toast-dismiss:hover{opacity:.7}.skra-toast-dismiss:focus-visible{outline-style:solid;outline-color:var(--skra-comp-toast-focus-ring-color);outline-width:var(--skra-comp-toast-focus-ring-width);outline-offset:2px}.skra-toast-dismiss-glyph{font-size:1.25em;line-height:1}@media(prefers-reduced-motion:reduce){:host{transition-duration:0ms}.skra-toast-dismiss{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
111
|
+
}
|
|
112
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraToast, decorators: [{
|
|
113
|
+
type: Component,
|
|
114
|
+
args: [{ selector: 'skra-toast', standalone: true, imports: [SkraToastIcon], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
|
|
115
|
+
// Variant Surface reflection (ADR 035 §3.5; ADR 021 — never [style.--var])
|
|
116
|
+
'[attr.data-skra-intent]': 'intent()',
|
|
117
|
+
'[attr.data-skra-emphasis]': 'emphasis()',
|
|
118
|
+
// Smart Slot layout reflection (ADR 020 v2.0 — slot-presence, State-Surface-exempt §3.6)
|
|
119
|
+
'[attr.data-skra-has-icon]': 'hasIcon() ? "" : null',
|
|
120
|
+
// NO `role` / NO `aria-live` here — the region announces (ADR 0052 §7).
|
|
121
|
+
// Pause/resume the auto-dismiss timer on hover + focus (WCAG 2.2.1 Timing Adjustable).
|
|
122
|
+
'(mouseenter)': 'pause()',
|
|
123
|
+
'(mouseleave)': 'resume()',
|
|
124
|
+
'(focusin)': 'pause()',
|
|
125
|
+
'(focusout)': 'resume()',
|
|
126
|
+
}, template: "@if (hasIcon()) {\n<span class=\"skra-toast-icon\">\n <ng-content select=\"[skra-toast-icon]\"></ng-content>\n</span>\n}\n\n<div class=\"skra-toast-content\" #content>\n <ng-content></ng-content>\n</div>\n\n@if (dismissible()) {\n<button type=\"button\" class=\"skra-toast-dismiss\" (click)=\"onDismiss()\">\n <span class=\"skra-toast-dismiss-glyph\" aria-hidden=\"true\">×</span>\n <ng-content select=\"[skra-toast-close-label]\"></ng-content>\n</button>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;align-items:flex-start;box-sizing:border-box;margin:0;gap:var(--skra-comp-toast-icon-gap);font-family:var(--skra-sys-typography-role-body-medium-font-family);font-size:var(--skra-sys-typography-role-body-medium-font-size);font-weight:var(--skra-sys-typography-role-body-medium-font-weight);line-height:var(--skra-sys-typography-role-body-medium-line-height);letter-spacing:var(--skra-sys-typography-role-body-medium-letter-spacing);background:var(--skra-comp-toast-background);color:var(--skra-comp-toast-foreground);border-color:var(--skra-comp-toast-border-color);border-width:var(--skra-comp-toast-border-width);border-style:var(--skra-comp-toast-border-style);border-radius:var(--skra-comp-toast-radius);padding-block:var(--skra-comp-toast-padding-block);padding-inline:var(--skra-comp-toast-padding-inline);box-shadow:var(--skra-comp-toast-shadow);opacity:1;transition:opacity var(--skra-comp-toast-motion-duration) var(--skra-comp-toast-motion-easing)}@starting-style{:host{opacity:0}}:host([data-skra-emphasis=high]){--skra-comp-toast-border-width: 0}:host([data-skra-intent=neutral][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-neutral-high, var(--skra-sys-color-neutral-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-neutral-high, var(--skra-sys-color-neutral-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-neutral-high, transparent )}:host([data-skra-intent=neutral][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-neutral-medium, var(--skra-sys-color-neutral-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-neutral-medium, var(--skra-sys-color-neutral-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-neutral-medium, var(--skra-sys-color-neutral-border) )}:host([data-skra-intent=info][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-info-high, var(--skra-sys-color-info-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-info-high, var(--skra-sys-color-info-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-info-high, transparent )}:host([data-skra-intent=info][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-info-medium, var(--skra-sys-color-info-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-info-medium, var(--skra-sys-color-info-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-info-medium, var(--skra-sys-color-info-border) )}:host([data-skra-intent=success][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-success-high, var(--skra-sys-color-success-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-success-high, var(--skra-sys-color-success-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-success-high, transparent )}:host([data-skra-intent=success][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-success-medium, var(--skra-sys-color-success-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-success-medium, var(--skra-sys-color-success-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-success-medium, var(--skra-sys-color-success-border) )}:host([data-skra-intent=warning][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-warning-high, var(--skra-sys-color-warning-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-warning-high, var(--skra-sys-color-warning-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-warning-high, transparent )}:host([data-skra-intent=warning][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-warning-medium, var(--skra-sys-color-warning-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-warning-medium, var(--skra-sys-color-warning-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-warning-medium, var(--skra-sys-color-warning-border) )}:host([data-skra-intent=danger][data-skra-emphasis=high]){--skra-comp-toast-background: var( --skra-comp-toast-background-danger-high, var(--skra-sys-color-danger-base) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-danger-high, var(--skra-sys-color-danger-contrast) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-danger-high, transparent )}:host([data-skra-intent=danger][data-skra-emphasis=medium]){--skra-comp-toast-background: var( --skra-comp-toast-background-danger-medium, var(--skra-sys-color-danger-soft) );--skra-comp-toast-foreground: var( --skra-comp-toast-foreground-danger-medium, var(--skra-sys-color-danger-strong) );--skra-comp-toast-border-color: var( --skra-comp-toast-border-color-danger-medium, var(--skra-sys-color-danger-border) )}.skra-toast-icon{display:inline-flex;flex-shrink:0;align-items:center;block-size:1lh;--skra-comp-icon-size: var(--skra-comp-toast-icon-size)}.skra-toast-content{flex:1 1 auto;min-inline-size:0}.skra-toast-dismiss{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;inline-size:var(--skra-comp-toast-dismiss-size);block-size:var(--skra-comp-toast-dismiss-size);margin:0;padding:0;border:0;background:transparent;color:inherit;cursor:pointer;border-radius:var(--skra-comp-toast-radius);transition:opacity var(--skra-comp-toast-motion-duration) var(--skra-comp-toast-motion-easing)}.skra-toast-dismiss:hover{opacity:.7}.skra-toast-dismiss:focus-visible{outline-style:solid;outline-color:var(--skra-comp-toast-focus-ring-color);outline-width:var(--skra-comp-toast-focus-ring-width);outline-offset:2px}.skra-toast-dismiss-glyph{font-size:1.25em;line-height:1}@media(prefers-reduced-motion:reduce){:host{transition-duration:0ms}.skra-toast-dismiss{transition:none}}\n"] }]
|
|
127
|
+
}], ctorParameters: () => [], propDecorators: { intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }], emphasis: [{ type: i0.Input, args: [{ isSignal: true, alias: "emphasis", required: false }] }], live: [{ type: i0.Input, args: [{ isSignal: true, alias: "live", required: false }] }], duration: [{ type: i0.Input, args: [{ isSignal: true, alias: "duration", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], iconSlot: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SkraToastIcon), { isSignal: true }] }], contentRef: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }] } });
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* `<skra-toast-region>` — the persistent, app-root manager for `<skra-toast>` (ADR 0044 §7
|
|
131
|
+
* non-anchored top-layer; ADR 0052 §7 announcer). Place ONCE near the app root; the consumer
|
|
132
|
+
* projects its own `@for` of toast models into it and owns the array (agnostic doctrine —
|
|
133
|
+
* a `ToastService` push/remove helper is a consumer recipe, NOT shipped here).
|
|
134
|
+
*
|
|
135
|
+
* Two mechanisms:
|
|
136
|
+
* - Announcement (ADR 0052 §7): provides `SkraToastAnnouncer` + renders TWO persistent,
|
|
137
|
+
* visually-hidden, role-only announcers (`role="status"` polite / `role="alert"` assertive).
|
|
138
|
+
* A toast pushes its text on appearance; identical repeated messages re-announce via a
|
|
139
|
+
* clear-then-set across two frames (forces a live-region mutation).
|
|
140
|
+
* - Top layer (ADR 0044 §7): a SINGLE `popover="manual"` stack (not `auto` — no light-dismiss /
|
|
141
|
+
* Escape collapse; not N popovers). Shown when non-empty; RE-ASSERTS top-layer order on each
|
|
142
|
+
* addition (`hidePopover()`+`showPopover()`) so a toast raised over an open modal `<dialog>`
|
|
143
|
+
* paints above it. Paint-only — never moves focus, never disturbs the dialog. Zero z-index.
|
|
144
|
+
*/
|
|
145
|
+
class SkraToastRegion {
|
|
146
|
+
placement = input('block-end inline-end', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
|
|
147
|
+
/** The two persistent role-only announcer buffers (ADR 0052 §7). */
|
|
148
|
+
politeMsg = signal('', ...(ngDevMode ? [{ debugName: "politeMsg" }] : /* istanbul ignore next */ []));
|
|
149
|
+
assertiveMsg = signal('', ...(ngDevMode ? [{ debugName: "assertiveMsg" }] : /* istanbul ignore next */ []));
|
|
150
|
+
toasts = contentChildren(SkraToast, ...(ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []));
|
|
151
|
+
stackEl = viewChild.required('stack');
|
|
152
|
+
/** Tracked locally — jsdom/SSR have no `:popover-open`; never read it back. */
|
|
153
|
+
shown = false;
|
|
154
|
+
prevCount = 0;
|
|
155
|
+
constructor() {
|
|
156
|
+
// Browser-only (afterRender family never runs on the server — SSR-safe, O-18). Reads the
|
|
157
|
+
// contentChildren signal so it re-runs whenever the visible toast set changes.
|
|
158
|
+
afterRenderEffect(() => {
|
|
159
|
+
// Re-assert keys on the COUNT, not toast identity: a remove+add in the SAME change-detection
|
|
160
|
+
// tick keeps `n` unchanged and skips the re-assert, so the new toast would not be raised above
|
|
161
|
+
// an already-open modal. Accepted for the auto-dismiss MVP (paint-only; announcement is
|
|
162
|
+
// unaffected); the cross-browser top-layer pass folds into O-9 (spec Open Question 3).
|
|
163
|
+
const n = this.toasts().length;
|
|
164
|
+
const stack = this.stackEl().nativeElement;
|
|
165
|
+
if (n > 0) {
|
|
166
|
+
if (n > this.prevCount && this.shown) {
|
|
167
|
+
// Re-assert top-layer order on additions (ADR 0044 §7) — drop then re-add to the top.
|
|
168
|
+
stack.hidePopover?.();
|
|
169
|
+
this.shown = false;
|
|
170
|
+
}
|
|
171
|
+
if (!this.shown) {
|
|
172
|
+
stack.showPopover?.();
|
|
173
|
+
this.shown = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (this.shown) {
|
|
177
|
+
stack.hidePopover?.();
|
|
178
|
+
this.shown = false;
|
|
179
|
+
}
|
|
180
|
+
this.prevCount = n;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* ADR 0052 §7 — write `text` into the matching persistent announcer. Clear-then-set across two
|
|
185
|
+
* animation frames so a repeated IDENTICAL message still mutates the live region and re-announces
|
|
186
|
+
* (the "repeated action" case). `off` toasts never call this.
|
|
187
|
+
*/
|
|
188
|
+
announce(text, politeness) {
|
|
189
|
+
const target = politeness === 'assertive' ? this.assertiveMsg : this.politeMsg;
|
|
190
|
+
target.set('');
|
|
191
|
+
requestAnimationFrame(() => requestAnimationFrame(() => target.set(text)));
|
|
192
|
+
}
|
|
193
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraToastRegion, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
194
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.7", type: SkraToastRegion, isStandalone: true, selector: "skra-toast-region", inputs: { placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-skra-placement": "placement()" } }, providers: [{ provide: SKRA_TOAST_ANNOUNCER, useExisting: SkraToastRegion }], queries: [{ propertyName: "toasts", predicate: SkraToast, isSignal: true }], viewQueries: [{ propertyName: "stackEl", first: true, predicate: ["stack"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"skra-toast-stack\" popover=\"manual\" #stack>\n <ng-content></ng-content>\n</div>\n\n<!--\n ADR 0052 \u00A77 \u2014 two PERSISTENT, role-only live announcers, held in normal flow (NOT inside the\n popover, so they are pre-armed and announce regardless of the stack's shown/paint state).\n role=\"status\" carries aria-live=polite + aria-atomic; role=\"alert\" carries assertive + atomic\n (implicitly \u2014 no explicit aria-live, ADR 0052 rule 2). Each holds one message at a time.\n-->\n<div class=\"skra-sr-only\" role=\"status\">{{ politeMsg() }}</div>\n<div class=\"skra-sr-only\" role=\"alert\">{{ assertiveMsg() }}</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.skra-toast-stack{position:fixed;margin:0;inset:auto;box-sizing:border-box;flex-direction:column;gap:var(--skra-comp-toast-region-gap);inline-size:max-content;max-inline-size:var(--skra-comp-toast-region-max-inline-size);border:0;padding:0;background:transparent}.skra-toast-stack:popover-open{display:flex}:host([data-skra-placement=\"block-end inline-end\"]) .skra-toast-stack{inset-block-end:var(--skra-comp-toast-region-inset-block);inset-inline-end:var(--skra-comp-toast-region-inset-inline);flex-direction:column}:host([data-skra-placement=\"block-end inline-start\"]) .skra-toast-stack{inset-block-end:var(--skra-comp-toast-region-inset-block);inset-inline-start:var(--skra-comp-toast-region-inset-inline);flex-direction:column}:host([data-skra-placement=\"block-end center\"]) .skra-toast-stack{inset-block-end:var(--skra-comp-toast-region-inset-block);inset-inline:0;margin-inline:auto;flex-direction:column}:host([data-skra-placement=\"block-start inline-end\"]) .skra-toast-stack{inset-block-start:var(--skra-comp-toast-region-inset-block);inset-inline-end:var(--skra-comp-toast-region-inset-inline);flex-direction:column-reverse}:host([data-skra-placement=\"block-start inline-start\"]) .skra-toast-stack{inset-block-start:var(--skra-comp-toast-region-inset-block);inset-inline-start:var(--skra-comp-toast-region-inset-inline);flex-direction:column-reverse}:host([data-skra-placement=\"block-start center\"]) .skra-toast-stack{inset-block-start:var(--skra-comp-toast-region-inset-block);inset-inline:0;margin-inline:auto;flex-direction:column-reverse}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
195
|
+
}
|
|
196
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: SkraToastRegion, decorators: [{
|
|
197
|
+
type: Component,
|
|
198
|
+
args: [{ selector: 'skra-toast-region', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, host: {
|
|
199
|
+
'[attr.data-skra-placement]': 'placement()',
|
|
200
|
+
}, providers: [{ provide: SKRA_TOAST_ANNOUNCER, useExisting: SkraToastRegion }], template: "<div class=\"skra-toast-stack\" popover=\"manual\" #stack>\n <ng-content></ng-content>\n</div>\n\n<!--\n ADR 0052 \u00A77 \u2014 two PERSISTENT, role-only live announcers, held in normal flow (NOT inside the\n popover, so they are pre-armed and announce regardless of the stack's shown/paint state).\n role=\"status\" carries aria-live=polite + aria-atomic; role=\"alert\" carries assertive + atomic\n (implicitly \u2014 no explicit aria-live, ADR 0052 rule 2). Each holds one message at a time.\n-->\n<div class=\"skra-sr-only\" role=\"status\">{{ politeMsg() }}</div>\n<div class=\"skra-sr-only\" role=\"alert\">{{ assertiveMsg() }}</div>\n", styles: ["@charset \"UTF-8\";:host{display:contents}.skra-toast-stack{position:fixed;margin:0;inset:auto;box-sizing:border-box;flex-direction:column;gap:var(--skra-comp-toast-region-gap);inline-size:max-content;max-inline-size:var(--skra-comp-toast-region-max-inline-size);border:0;padding:0;background:transparent}.skra-toast-stack:popover-open{display:flex}:host([data-skra-placement=\"block-end inline-end\"]) .skra-toast-stack{inset-block-end:var(--skra-comp-toast-region-inset-block);inset-inline-end:var(--skra-comp-toast-region-inset-inline);flex-direction:column}:host([data-skra-placement=\"block-end inline-start\"]) .skra-toast-stack{inset-block-end:var(--skra-comp-toast-region-inset-block);inset-inline-start:var(--skra-comp-toast-region-inset-inline);flex-direction:column}:host([data-skra-placement=\"block-end center\"]) .skra-toast-stack{inset-block-end:var(--skra-comp-toast-region-inset-block);inset-inline:0;margin-inline:auto;flex-direction:column}:host([data-skra-placement=\"block-start inline-end\"]) .skra-toast-stack{inset-block-start:var(--skra-comp-toast-region-inset-block);inset-inline-end:var(--skra-comp-toast-region-inset-inline);flex-direction:column-reverse}:host([data-skra-placement=\"block-start inline-start\"]) .skra-toast-stack{inset-block-start:var(--skra-comp-toast-region-inset-block);inset-inline-start:var(--skra-comp-toast-region-inset-inline);flex-direction:column-reverse}:host([data-skra-placement=\"block-start center\"]) .skra-toast-stack{inset-block-start:var(--skra-comp-toast-region-inset-block);inset-inline:0;margin-inline:auto;flex-direction:column-reverse}\n"] }]
|
|
201
|
+
}], ctorParameters: () => [], propDecorators: { placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], toasts: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SkraToast), { isSignal: true }] }], stackEl: [{ type: i0.ViewChild, args: ['stack', { isSignal: true }] }] } });
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Generated bundle index. Do not edit.
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
export { SKRA_TOAST_ANNOUNCER, SkraToast, SkraToastIcon, SkraToastRegion };
|
|
208
|
+
//# sourceMappingURL=skra-ui-toast.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skra-ui-toast.mjs","sources":["../../../../libs/ui/toast/src/lib/toast.directives.ts","../../../../libs/ui/toast/src/lib/toast/toast-announcer.ts","../../../../libs/ui/toast/src/lib/toast/toast.ts","../../../../libs/ui/toast/src/lib/toast/toast.html","../../../../libs/ui/toast/src/lib/toast/toast-region.ts","../../../../libs/ui/toast/src/lib/toast/toast-region.html","../../../../libs/ui/toast/src/skra-ui-toast.ts"],"sourcesContent":["import { Directive } from '@angular/core';\n\n/**\n * Marker directive for the leading icon slot of `<skra-toast>`.\n * Detected via `contentChild` (ADR 020 Smart Slots v2.0) to toggle the icon\n * wrapper without emitting an empty DOM node. Feedback-local (not the button\n * lib's `[skra-start]`) to avoid a cross-lib marker dependency — mirrors\n * `[skra-alert-icon]` / `[skra-badge-icon]`.\n */\n@Directive({\n selector: '[skra-toast-icon]',\n standalone: true,\n})\nexport class SkraToastIcon {}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * Politeness routed to the toast region's role-only announcer (ADR 0052 §7).\n * `off` never reaches the announcer (no announcement), so the seam only carries\n * the two announced levels.\n */\nexport type SkraToastPoliteness = 'polite' | 'assertive';\n\n/**\n * The ADR 0052 §7 announcement seam. Provided by `<skra-toast-region>` and injected\n * (optionally) by each projected `<skra-toast>`. The toast pushes its message text on\n * appearance; the region writes it into the matching PERSISTENT, role-only announcer\n * (`role=\"status\"` for polite / `role=\"alert\"` for assertive) — never a role/`aria-live`\n * on the ephemeral toast host. This is why a live region pre-exists the content it\n * announces (WCAG 4.1.3): the announcers are rendered from the region's first render.\n *\n * Narrow seam (like `SkraSelectPanel` / `SkraFieldContext`): the toast depends on the\n * interface, not the concrete region. Content-projected toasts inject from their DECLARED\n * parent element (= the region), the same DI mechanism as `SkraFieldContext` (ADR 013).\n */\nexport interface SkraToastAnnouncer {\n /** Announce `text` at the given politeness by writing it into the matching live region. */\n announce(text: string, politeness: SkraToastPoliteness): void;\n}\n\nexport const SKRA_TOAST_ANNOUNCER = new InjectionToken<SkraToastAnnouncer>(\n 'SKRA_TOAST_ANNOUNCER',\n);\n","import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n ViewEncapsulation,\n afterNextRender,\n booleanAttribute,\n computed,\n contentChild,\n inject,\n input,\n output,\n viewChild,\n} from '@angular/core';\nimport { SkraToastIcon } from '../toast.directives';\nimport {\n SKRA_TOAST_ANNOUNCER,\n type SkraToastPoliteness,\n} from './toast-announcer';\n\n/**\n * ToastIntent — semantic feedback meaning (ADR 035 §3.1, downward subset). Excludes the\n * action intents `primary`/`secondary`. Same enum as Alert/Badge.\n */\nexport type ToastIntent = 'neutral' | 'info' | 'success' | 'warning' | 'danger';\n\n/** ToastEmphasis — `high` = filled, `medium` = soft. `low` deferred (ADR 035 §3.1). */\nexport type ToastEmphasis = 'high' | 'medium';\n\n/**\n * ToastLive — screen-reader announcement politeness (ADR 0052). For Toast the politeness\n * is routed to the REGION's announcer (ADR 0052 §7); it does NOT set a role on this host.\n * Default `polite` (toasts are always dynamically presented — ADR 0052 §4). `assertive`\n * for error toasts, set explicitly by the consumer — NEVER auto-derived from `intent`.\n */\nexport type ToastLive = 'off' | 'polite' | 'assertive';\n\n/**\n * `<skra-toast>` — an ephemeral, auto-dismissing feedback message rendered inside a\n * `<skra-toast-region>` top-layer stack (Feedback/Status family, ADR 0052; Tier 2 overlay,\n * ADR 0044). Visually an Alert (intent×emphasis seam, 4th consumer) with a timer.\n *\n * Announcement (ADR 0052 §7): this host carries NO `role`/`aria-live`. On appearance the\n * toast pushes its message text into the region's persistent role-only announcer via the\n * injected `SkraToastAnnouncer`, at the politeness of `live` (`off` → no announcement).\n *\n * Auto-dismiss (WCAG 2.2.1): after `duration` ms the toast emits `(dismissed)`. The timer\n * PAUSES on hover and on focus-within and resumes with the remaining time; `duration=0`\n * disables it (persistent — pair with `dismissible`). Forge does NOT self-remove — the\n * consumer removes the model from its array on `(dismissed)` (agnostic doctrine; the Alert\n * precedent). `(dismissed)` is the single removal channel (timer elapse OR close control).\n */\n@Component({\n selector: 'skra-toast',\n standalone: true,\n imports: [SkraToastIcon],\n templateUrl: './toast.html',\n styleUrl: './toast.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n // Variant Surface reflection (ADR 035 §3.5; ADR 021 — never [style.--var])\n '[attr.data-skra-intent]': 'intent()',\n '[attr.data-skra-emphasis]': 'emphasis()',\n // Smart Slot layout reflection (ADR 020 v2.0 — slot-presence, State-Surface-exempt §3.6)\n '[attr.data-skra-has-icon]': 'hasIcon() ? \"\" : null',\n // NO `role` / NO `aria-live` here — the region announces (ADR 0052 §7).\n // Pause/resume the auto-dismiss timer on hover + focus (WCAG 2.2.1 Timing Adjustable).\n '(mouseenter)': 'pause()',\n '(mouseleave)': 'resume()',\n '(focusin)': 'pause()',\n '(focusout)': 'resume()',\n },\n})\nexport class SkraToast {\n // --- Variant Surface (ADR 035) ---\n readonly intent = input<ToastIntent>('neutral');\n readonly emphasis = input<ToastEmphasis>('medium');\n\n // --- Announcement (ADR 0052 §7) ---\n readonly live = input<ToastLive>('polite');\n\n // --- Auto-dismiss + dismissal ---\n readonly duration = input<number>(5000);\n readonly dismissible = input(false, { transform: booleanAttribute });\n readonly dismissed = output<void>();\n\n // --- Smart Slots v2.0 (ADR 020) ---\n readonly iconSlot = contentChild(SkraToastIcon);\n readonly hasIcon = computed(() => !!this.iconSlot());\n\n /** The message column — its text is the announcement (excludes the icon + sr-only close label). */\n private readonly contentRef =\n viewChild.required<ElementRef<HTMLElement>>('content');\n\n private readonly announcer = inject(SKRA_TOAST_ANNOUNCER, {\n optional: true,\n });\n\n private timerId: ReturnType<typeof setTimeout> | null = null;\n private remaining = 0;\n private startedAt = 0;\n\n constructor() {\n afterNextRender(() => {\n // Announce on appearance (browser-only; projected content is present post-render).\n const live = this.live();\n if (live !== 'off' && this.announcer) {\n const text = this.contentRef().nativeElement.textContent?.trim() ?? '';\n if (text) this.announcer.announce(text, live as SkraToastPoliteness);\n }\n // Start the auto-dismiss countdown (timers are browser-only — SSR-safe by construction).\n this.remaining = this.duration();\n this.start();\n });\n inject(DestroyRef).onDestroy(() => this.clear());\n }\n\n protected onDismiss(): void {\n this.clear();\n this.dismissed.emit();\n }\n\n /** Pause the countdown, banking the remaining time (WCAG 2.2.1). */\n protected pause(): void {\n if (this.timerId === null) return;\n clearTimeout(this.timerId);\n this.timerId = null;\n this.remaining -= Date.now() - this.startedAt;\n }\n\n /** Resume the countdown with the banked remaining time. */\n protected resume(): void {\n if (this.timerId !== null) return;\n this.start();\n }\n\n private start(): void {\n if (this.remaining <= 0) return; // duration=0 (or fully elapsed) → persistent\n this.startedAt = Date.now();\n this.timerId = setTimeout(() => {\n this.timerId = null;\n this.dismissed.emit();\n }, this.remaining);\n }\n\n private clear(): void {\n if (this.timerId !== null) {\n clearTimeout(this.timerId);\n this.timerId = null;\n }\n }\n}\n","@if (hasIcon()) {\n<span class=\"skra-toast-icon\">\n <ng-content select=\"[skra-toast-icon]\"></ng-content>\n</span>\n}\n\n<div class=\"skra-toast-content\" #content>\n <ng-content></ng-content>\n</div>\n\n@if (dismissible()) {\n<button type=\"button\" class=\"skra-toast-dismiss\" (click)=\"onDismiss()\">\n <span class=\"skra-toast-dismiss-glyph\" aria-hidden=\"true\">×</span>\n <ng-content select=\"[skra-toast-close-label]\"></ng-content>\n</button>\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n ViewEncapsulation,\n afterRenderEffect,\n contentChildren,\n input,\n signal,\n viewChild,\n} from '@angular/core';\nimport { SkraToast } from './toast';\nimport {\n SKRA_TOAST_ANNOUNCER,\n type SkraToastAnnouncer,\n type SkraToastPoliteness,\n} from './toast-announcer';\n\n/**\n * ToastPlacement — viewport corner, logical (RTL-correct). `block-start`/`block-end` = the\n * top/bottom edge; `inline-start`/`center`/`inline-end` = the horizontal position.\n */\nexport type ToastPlacement =\n | 'block-start inline-start'\n | 'block-start center'\n | 'block-start inline-end'\n | 'block-end inline-start'\n | 'block-end center'\n | 'block-end inline-end';\n\n/**\n * `<skra-toast-region>` — the persistent, app-root manager for `<skra-toast>` (ADR 0044 §7\n * non-anchored top-layer; ADR 0052 §7 announcer). Place ONCE near the app root; the consumer\n * projects its own `@for` of toast models into it and owns the array (agnostic doctrine —\n * a `ToastService` push/remove helper is a consumer recipe, NOT shipped here).\n *\n * Two mechanisms:\n * - Announcement (ADR 0052 §7): provides `SkraToastAnnouncer` + renders TWO persistent,\n * visually-hidden, role-only announcers (`role=\"status\"` polite / `role=\"alert\"` assertive).\n * A toast pushes its text on appearance; identical repeated messages re-announce via a\n * clear-then-set across two frames (forces a live-region mutation).\n * - Top layer (ADR 0044 §7): a SINGLE `popover=\"manual\"` stack (not `auto` — no light-dismiss /\n * Escape collapse; not N popovers). Shown when non-empty; RE-ASSERTS top-layer order on each\n * addition (`hidePopover()`+`showPopover()`) so a toast raised over an open modal `<dialog>`\n * paints above it. Paint-only — never moves focus, never disturbs the dialog. Zero z-index.\n */\n@Component({\n selector: 'skra-toast-region',\n standalone: true,\n templateUrl: './toast-region.html',\n styleUrl: './toast-region.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.Emulated,\n host: {\n '[attr.data-skra-placement]': 'placement()',\n },\n providers: [{ provide: SKRA_TOAST_ANNOUNCER, useExisting: SkraToastRegion }],\n})\nexport class SkraToastRegion implements SkraToastAnnouncer {\n readonly placement = input<ToastPlacement>('block-end inline-end');\n\n /** The two persistent role-only announcer buffers (ADR 0052 §7). */\n protected readonly politeMsg = signal('');\n protected readonly assertiveMsg = signal('');\n\n private readonly toasts = contentChildren(SkraToast);\n private readonly stackEl =\n viewChild.required<ElementRef<HTMLElement>>('stack');\n\n /** Tracked locally — jsdom/SSR have no `:popover-open`; never read it back. */\n private shown = false;\n private prevCount = 0;\n\n constructor() {\n // Browser-only (afterRender family never runs on the server — SSR-safe, O-18). Reads the\n // contentChildren signal so it re-runs whenever the visible toast set changes.\n afterRenderEffect(() => {\n // Re-assert keys on the COUNT, not toast identity: a remove+add in the SAME change-detection\n // tick keeps `n` unchanged and skips the re-assert, so the new toast would not be raised above\n // an already-open modal. Accepted for the auto-dismiss MVP (paint-only; announcement is\n // unaffected); the cross-browser top-layer pass folds into O-9 (spec Open Question 3).\n const n = this.toasts().length;\n const stack = this.stackEl().nativeElement;\n if (n > 0) {\n if (n > this.prevCount && this.shown) {\n // Re-assert top-layer order on additions (ADR 0044 §7) — drop then re-add to the top.\n stack.hidePopover?.();\n this.shown = false;\n }\n if (!this.shown) {\n stack.showPopover?.();\n this.shown = true;\n }\n } else if (this.shown) {\n stack.hidePopover?.();\n this.shown = false;\n }\n this.prevCount = n;\n });\n }\n\n /**\n * ADR 0052 §7 — write `text` into the matching persistent announcer. Clear-then-set across two\n * animation frames so a repeated IDENTICAL message still mutates the live region and re-announces\n * (the \"repeated action\" case). `off` toasts never call this.\n */\n announce(text: string, politeness: SkraToastPoliteness): void {\n const target =\n politeness === 'assertive' ? this.assertiveMsg : this.politeMsg;\n target.set('');\n requestAnimationFrame(() => requestAnimationFrame(() => target.set(text)));\n }\n}\n","<div class=\"skra-toast-stack\" popover=\"manual\" #stack>\n <ng-content></ng-content>\n</div>\n\n<!--\n ADR 0052 §7 — two PERSISTENT, role-only live announcers, held in normal flow (NOT inside the\n popover, so they are pre-armed and announce regardless of the stack's shown/paint state).\n role=\"status\" carries aria-live=polite + aria-atomic; role=\"alert\" carries assertive + atomic\n (implicitly — no explicit aria-live, ADR 0052 rule 2). Each holds one message at a time.\n-->\n<div class=\"skra-sr-only\" role=\"status\">{{ politeMsg() }}</div>\n<div class=\"skra-sr-only\" role=\"alert\">{{ assertiveMsg() }}</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|