@kouji-ui/core 0.0.4 → 0.1.2

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 (127) hide show
  1. package/fesm2022/kouji-ui-core.mjs +26789 -0
  2. package/fesm2022/kouji-ui-core.mjs.map +1 -0
  3. package/icon/icon.css +40 -0
  4. package/package.json +20 -9
  5. package/types/kouji-ui-core.d.ts +13545 -0
  6. package/typography/prose.css +312 -0
  7. package/.turbo/turbo-build.log +0 -27
  8. package/CHANGELOG.md +0 -21
  9. package/ng-package.json +0 -7
  10. package/src/a11y/.gitkeep +0 -0
  11. package/src/a11y/aria-describedby.spec.ts +0 -55
  12. package/src/a11y/aria-describedby.ts +0 -33
  13. package/src/a11y/focus-trap.spec.ts +0 -86
  14. package/src/a11y/focus-trap.ts +0 -70
  15. package/src/a11y/index.ts +0 -9
  16. package/src/a11y/live-region.spec.ts +0 -58
  17. package/src/a11y/live-region.ts +0 -48
  18. package/src/a11y/roving-tabindex.spec.ts +0 -62
  19. package/src/a11y/roving-tabindex.ts +0 -104
  20. package/src/a11y/visually-hidden.spec.ts +0 -34
  21. package/src/a11y/visually-hidden.ts +0 -24
  22. package/src/accordion/accordion.context.ts +0 -22
  23. package/src/accordion/accordion.spec.ts +0 -32
  24. package/src/accordion/accordion.ts +0 -129
  25. package/src/accordion/index.ts +0 -2
  26. package/src/avatar/avatar.spec.ts +0 -18
  27. package/src/avatar/avatar.ts +0 -26
  28. package/src/avatar/index.ts +0 -1
  29. package/src/badge/badge.spec.ts +0 -20
  30. package/src/badge/badge.ts +0 -14
  31. package/src/badge/index.ts +0 -1
  32. package/src/button/button.example.ts +0 -30
  33. package/src/button/button.finance.example.ts +0 -37
  34. package/src/button/button.retro.example.ts +0 -47
  35. package/src/button/button.sizes.example.ts +0 -25
  36. package/src/button/button.spec.ts +0 -32
  37. package/src/button/button.ts +0 -44
  38. package/src/button/index.ts +0 -1
  39. package/src/chart/chart.spec.ts +0 -27
  40. package/src/chart/chart.ts +0 -48
  41. package/src/chart/index.ts +0 -1
  42. package/src/checkbox/checkbox.spec.ts +0 -35
  43. package/src/checkbox/checkbox.ts +0 -82
  44. package/src/checkbox/index.ts +0 -1
  45. package/src/dialog/dialog.confirm.example.ts +0 -57
  46. package/src/dialog/dialog.context.ts +0 -11
  47. package/src/dialog/dialog.example.ts +0 -40
  48. package/src/dialog/dialog.finance.example.ts +0 -47
  49. package/src/dialog/dialog.retro.example.ts +0 -46
  50. package/src/dialog/dialog.service.ts +0 -176
  51. package/src/dialog/dialog.spec.ts +0 -122
  52. package/src/dialog/dialog.ts +0 -201
  53. package/src/dialog/index.ts +0 -9
  54. package/src/example-components.ts +0 -33
  55. package/src/form/form.context.ts +0 -3
  56. package/src/form/form.spec.ts +0 -25
  57. package/src/form/form.ts +0 -46
  58. package/src/form/index.ts +0 -2
  59. package/src/input/index.ts +0 -1
  60. package/src/input/input.spec.ts +0 -43
  61. package/src/input/input.ts +0 -80
  62. package/src/menu/index.ts +0 -2
  63. package/src/menu/menu.context.ts +0 -10
  64. package/src/menu/menu.example.ts +0 -39
  65. package/src/menu/menu.spec.ts +0 -168
  66. package/src/menu/menu.ts +0 -176
  67. package/src/popover/index.ts +0 -12
  68. package/src/popover/popover.context.ts +0 -18
  69. package/src/popover/popover.example.ts +0 -50
  70. package/src/popover/popover.finance.example.ts +0 -52
  71. package/src/popover/popover.retro.example.ts +0 -53
  72. package/src/popover/popover.spec.ts +0 -52
  73. package/src/popover/popover.ts +0 -124
  74. package/src/primitives/.gitkeep +0 -0
  75. package/src/primitives/forms/form-control.spec.ts +0 -59
  76. package/src/primitives/forms/form-control.ts +0 -90
  77. package/src/primitives/forms/index.ts +0 -1
  78. package/src/primitives/index.ts +0 -3
  79. package/src/primitives/interaction/disabled.spec.ts +0 -47
  80. package/src/primitives/interaction/disabled.ts +0 -24
  81. package/src/primitives/interaction/focus-ring.spec.ts +0 -62
  82. package/src/primitives/interaction/focus-ring.ts +0 -57
  83. package/src/primitives/interaction/index.ts +0 -2
  84. package/src/primitives/overlay/index.ts +0 -1
  85. package/src/primitives/overlay/overlay.spec.ts +0 -90
  86. package/src/primitives/overlay/overlay.ts +0 -106
  87. package/src/public-api.ts +0 -35
  88. package/src/radio/index.ts +0 -2
  89. package/src/radio/radio.context.ts +0 -3
  90. package/src/radio/radio.spec.ts +0 -36
  91. package/src/radio/radio.ts +0 -55
  92. package/src/select/index.ts +0 -2
  93. package/src/select/select.context.ts +0 -3
  94. package/src/select/select.spec.ts +0 -145
  95. package/src/select/select.ts +0 -250
  96. package/src/styles/docs-themes.css +0 -82
  97. package/src/table/index.ts +0 -1
  98. package/src/table/table.spec.ts +0 -113
  99. package/src/table/table.ts +0 -126
  100. package/src/tabs/index.ts +0 -2
  101. package/src/tabs/tabs.context.ts +0 -9
  102. package/src/tabs/tabs.spec.ts +0 -40
  103. package/src/tabs/tabs.ts +0 -130
  104. package/src/test-setup.ts +0 -5
  105. package/src/toast/index.ts +0 -21
  106. package/src/toast/toast.example.ts +0 -134
  107. package/src/toast/toast.finance.example.ts +0 -68
  108. package/src/toast/toast.retro.example.ts +0 -69
  109. package/src/toast/toast.service.ts +0 -153
  110. package/src/toast/toast.spec.ts +0 -95
  111. package/src/toast/toast.strategy.ts +0 -102
  112. package/src/toast/toast.ts +0 -307
  113. package/src/toast/toast.types.ts +0 -5
  114. package/src/toggle/index.ts +0 -1
  115. package/src/toggle/toggle.spec.ts +0 -24
  116. package/src/toggle/toggle.ts +0 -40
  117. package/src/tooltip/index.ts +0 -5
  118. package/src/tooltip/tooltip.example.ts +0 -46
  119. package/src/tooltip/tooltip.finance.example.ts +0 -54
  120. package/src/tooltip/tooltip.placements.example.ts +0 -56
  121. package/src/tooltip/tooltip.retro.example.ts +0 -65
  122. package/src/tooltip/tooltip.spec.ts +0 -104
  123. package/src/tooltip/tooltip.ts +0 -129
  124. package/tsconfig.lib.json +0 -13
  125. package/tsconfig.lib.prod.json +0 -11
  126. package/tsconfig.spec.json +0 -10
  127. package/vite.config.ts +0 -19
@@ -1,68 +0,0 @@
1
- import { Component, inject } from '@angular/core';
2
- import { NgTemplateOutlet } from '@angular/common';
3
- import { KjToastViewport, KjToast } from './toast';
4
- import { KjToastService } from './toast.service';
5
-
6
- @Component({
7
- selector: 'kj-example-toast-finance',
8
- standalone: true,
9
- imports: [KjToastViewport, KjToast, NgTemplateOutlet],
10
- styleUrls: ['../styles/docs-themes.css'],
11
- host: { class: 'kj-theme-finance' },
12
- styles: [`
13
- :host { display: block; padding: 2rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 260px; position: relative; }
14
- .row { display: flex; gap: 0.625rem; flex-wrap: wrap; }
15
- button {
16
- padding: 0.4rem 1rem; font-family: inherit; font-size: 0.8125rem; font-weight: 500;
17
- border-radius: var(--kj-radius-md); border: var(--kj-btn-border); cursor: pointer; transition: var(--kj-transition); line-height: 1.5;
18
- }
19
- .btn-success { background: #22c55e; color: #fff; border-color: #22c55e; }
20
- .btn-success:hover { background: #16a34a; }
21
- .btn-error { background: #ef4444; color: #fff; border-color: #ef4444; }
22
- .btn-error:hover { background: #dc2626; }
23
- .btn-warning { background: #f59e0b; color: #fff; border-color: #f59e0b; }
24
- .btn-warning:hover { background: #d97706; }
25
- .btn-info { background: var(--kj-accent); color: var(--kj-accent-on); border-color: var(--kj-accent); }
26
- .btn-info:hover { background: #2563eb; }
27
- [kjToastViewport] {
28
- position: absolute; bottom: 1rem; right: 1rem;
29
- display: flex; flex-direction: column; gap: 0.5rem; width: 18rem;
30
- list-style: none; padding: 0; margin: 0;
31
- }
32
- [kjToast] {
33
- display: flex; align-items: center; justify-content: space-between;
34
- padding: 0.75rem 1rem; font-size: 0.8125rem; border-radius: var(--kj-radius-lg);
35
- box-shadow: var(--kj-shadow-sm); animation: slideIn 0.2s ease;
36
- }
37
- @keyframes slideIn { from { opacity: 0; transform: translateX(1rem); } to { opacity: 1; transform: translateX(0); } }
38
- [data-variant="success"] { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
39
- [data-variant="destructive"] { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
40
- [data-variant="warning"] { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
41
- [data-variant="default"] { background: #eff6ff; color: #1e40af; border: 1px solid #bfdbfe; }
42
- .close { background: none; border: none; cursor: pointer; font-size: 1rem; color: inherit; padding: 0 0 0 0.5rem; opacity: 0.5; }
43
- .close:hover { opacity: 1; }
44
- `],
45
- template: `
46
- <div class="row">
47
- <button class="btn-success" (click)="toast.success('Transaction completed.')">Success</button>
48
- <button class="btn-error" (click)="toast.error('Payment declined.')">Error</button>
49
- <button class="btn-warning" (click)="toast.warning('Session expiring soon.')">Warning</button>
50
- <button class="btn-info" (click)="toast.info('Statement ready to download.')">Info</button>
51
- </div>
52
- <ol kjToastViewport [kjToastDefaultTemplate]="defaultTpl" #vp="kjToastViewport" aria-label="Notifications">
53
- @for (r of vp.renderable(); track r.id) {
54
- <li><ng-container *ngTemplateOutlet="r.template; context: r.context" /></li>
55
- }
56
- </ol>
57
-
58
- <ng-template #defaultTpl let-ctx>
59
- <div kjToast [kjToastVariant]="ctx.variant" [kjToastId]="ctx.id">
60
- <span>{{ ctx.message }}</span>
61
- <button class="close" (click)="ctx.dismiss()" aria-label="Dismiss">×</button>
62
- </div>
63
- </ng-template>
64
- `,
65
- })
66
- export class ToastFinanceExample {
67
- readonly toast = inject(KjToastService);
68
- }
@@ -1,69 +0,0 @@
1
- import { Component, inject } from '@angular/core';
2
- import { NgTemplateOutlet } from '@angular/common';
3
- import { KjToastViewport, KjToast } from './toast';
4
- import { KjToastService } from './toast.service';
5
-
6
- @Component({
7
- selector: 'kj-example-toast-retro',
8
- standalone: true,
9
- imports: [KjToastViewport, KjToast, NgTemplateOutlet],
10
- styleUrls: ['../styles/docs-themes.css'],
11
- host: { class: 'kj-theme-retro' },
12
- styles: [`
13
- :host { display: block; padding: 2rem; background: var(--kj-bg); font-family: var(--kj-font); min-height: 260px; position: relative; color: var(--kj-text); }
14
- .row { display: flex; gap: 0.625rem; flex-wrap: wrap; }
15
- button {
16
- padding: 0.35rem 0.875rem; font-family: var(--kj-font); font-size: 0.75rem;
17
- font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
18
- border: var(--kj-btn-border); border-radius: 0; cursor: pointer;
19
- box-shadow: var(--kj-shadow-sm); transition: var(--kj-transition);
20
- }
21
- button:hover { transform: translate(-1px, -1px); box-shadow: var(--kj-shadow-md); }
22
- .btn-ok { background: #16a34a; color: #fff; }
23
- .btn-err { background: #dc2626; color: #fff; }
24
- .btn-warn { background: #d97706; color: #fff; }
25
- .btn-info { background: #1d4ed8; color: #fff; border-color: #1d4ed8; box-shadow: 3px 3px 0 #1d4ed8; }
26
- .btn-info:hover { box-shadow: 4px 4px 0 #1d4ed8; }
27
- [kjToastViewport] {
28
- position: absolute; bottom: 1rem; right: 1rem;
29
- display: flex; flex-direction: column; gap: 0.5rem; width: 18rem;
30
- list-style: none; padding: 0; margin: 0;
31
- }
32
- [kjToast] {
33
- display: flex; align-items: center; justify-content: space-between;
34
- padding: 0.625rem 0.875rem; font-family: var(--kj-font); font-size: 0.75rem;
35
- border: var(--kj-btn-border); background: var(--kj-bg); color: var(--kj-text); box-shadow: var(--kj-shadow-md);
36
- animation: slideIn 0.15s ease;
37
- }
38
- @keyframes slideIn { from { opacity: 0; transform: translateX(1rem); } to { opacity: 1; transform: translateX(0); } }
39
- [data-variant="success"] { border-color: #16a34a; }
40
- [data-variant="destructive"] { border-color: #dc2626; }
41
- [data-variant="warning"] { border-color: #d97706; }
42
- [data-variant="default"] { border-color: #1d4ed8; }
43
- .close { background: none; border: none; box-shadow: none; cursor: pointer; font-size: 1rem; padding: 0 0 0 0.5rem; transition: none; font-family: inherit; }
44
- .close:hover { transform: none; }
45
- `],
46
- template: `
47
- <div class="row">
48
- <button class="btn-ok" (click)="toast.success('File saved!')">OK</button>
49
- <button class="btn-err" (click)="toast.error('Access denied!')">ERR</button>
50
- <button class="btn-warn" (click)="toast.warning('Low disk space.')">WARN</button>
51
- <button class="btn-info" (click)="toast.info('Update available.')">INFO</button>
52
- </div>
53
- <ol kjToastViewport [kjToastDefaultTemplate]="defaultTpl" #vp="kjToastViewport" aria-label="Notifications">
54
- @for (r of vp.renderable(); track r.id) {
55
- <li><ng-container *ngTemplateOutlet="r.template; context: r.context" /></li>
56
- }
57
- </ol>
58
-
59
- <ng-template #defaultTpl let-ctx>
60
- <div kjToast [kjToastVariant]="ctx.variant" [kjToastId]="ctx.id">
61
- <span>{{ ctx.message }}</span>
62
- <button class="close" (click)="ctx.dismiss()" aria-label="Dismiss">×</button>
63
- </div>
64
- </ng-template>
65
- `,
66
- })
67
- export class ToastRetroExample {
68
- readonly toast = inject(KjToastService);
69
- }
@@ -1,153 +0,0 @@
1
- import { Injectable, TemplateRef, inject, signal } from '@angular/core';
2
- import { KJ_TOAST_STRATEGY } from './toast.strategy';
3
-
4
- export type KjToastVariant = 'default' | 'success' | 'destructive' | 'warning';
5
-
6
- /**
7
- * Shape `[kjToastDefaultTemplate]` and per-call templates receive via `ngTemplateOutlet`.
8
- * Inherits every `KjToastContext` field for `let-id="id"`-style bindings, plus `$implicit`
9
- * for the canonical `<ng-template let-ctx>` form.
10
- */
11
- export interface KjToastTemplateContext<TData = unknown> extends KjToastContext<TData> {
12
- $implicit: KjToastContext<TData>;
13
- }
14
-
15
- /** Context object passed to a toast template. Available via `let-ctx` (or `let-ctx="$implicit"`). */
16
- export interface KjToastContext<TData = unknown> {
17
- /** Unique id for this toast. */
18
- readonly id: string;
19
- /** Variant set when the toast was shown. */
20
- readonly variant: KjToastVariant;
21
- /** Plain text message — only set when shown via the string-based API. */
22
- readonly message?: string;
23
- /** Optional title set via options. */
24
- readonly title?: string;
25
- /** Arbitrary client payload set via options.data. Strongly typed via the TemplateRef generic. */
26
- readonly data?: TData;
27
- /** Dismiss this toast. Bound — safe to use directly in templates. */
28
- dismiss(): void;
29
- }
30
-
31
- /** Options for `KjToastService.show()` and the `success`/`error`/`warning`/`info` shorthands. */
32
- export interface KjToastOptions<TData = unknown> {
33
- /** Override the auto-generated id. */
34
- id?: string;
35
- /** Optional title rendered above the message. */
36
- title?: string;
37
- /** Visual / semantic variant. Defaults to `'default'`. */
38
- variant?: KjToastVariant;
39
- /** Auto-dismiss delay in ms. `0` = persistent. Defaults to `5000`. */
40
- duration?: number;
41
- /** Arbitrary payload exposed to the template via `ctx.data`. */
42
- data?: TData;
43
- }
44
-
45
- /** Internal queue item — tracked by the service, consumed by `KjToastViewport`. */
46
- export interface KjToastItem<TData = unknown> {
47
- readonly id: string;
48
- readonly variant: KjToastVariant;
49
- readonly duration: number;
50
- readonly message?: string;
51
- readonly title?: string;
52
- readonly data?: TData;
53
- /** Per-call template, when shown via `show(template, …)`. Otherwise the viewport's default template renders. */
54
- readonly template?: TemplateRef<KjToastTemplateContext<TData>>;
55
- }
56
-
57
- /**
58
- * Programmatic API for toast notifications.
59
- *
60
- * Two ways to show a toast:
61
- * - `show(template, options)` — full template control. Markup, layout, icons, action buttons all owned by the client.
62
- * - `show(message, options)` — string shorthand. Uses the viewport's `[kjToastDefaultTemplate]`.
63
- *
64
- * @example
65
- * ```ts
66
- * private readonly toast = inject(KjToastService);
67
- *
68
- * save() { this.toast.success('Saved!'); }
69
- * undoable() {
70
- * this.toast.show(this.undoTpl, { duration: 8000, data: { entityId: 42 } });
71
- * }
72
- * ```
73
- * @category Core/Overlays
74
- */
75
- @Injectable({ providedIn: 'root' })
76
- export class KjToastService {
77
- private readonly strategy = inject(KJ_TOAST_STRATEGY);
78
- private readonly _toasts = signal<KjToastItem[]>([]);
79
- /** Live list of active toasts. Read by `[kjToastViewport]`. */
80
- readonly toasts = this._toasts.asReadonly();
81
-
82
- private readonly timers = new Map<string, ReturnType<typeof setTimeout>>();
83
-
84
- /** Show a toast with a custom template. Returns the toast id. */
85
- show<T = unknown>(template: TemplateRef<KjToastTemplateContext<T>>, options?: KjToastOptions<T>): string;
86
- /** Show a toast with a plain text message. The viewport's default template renders it. */
87
- show(message: string, options?: KjToastOptions): string;
88
- show(content: TemplateRef<KjToastTemplateContext> | string, options: KjToastOptions = {}): string {
89
- const id = options.id ?? crypto.randomUUID();
90
- const isTemplate = content instanceof TemplateRef;
91
- const toast: KjToastItem = {
92
- id,
93
- variant: options.variant ?? 'default',
94
- duration: options.duration ?? this.strategy.duration,
95
- title: options.title,
96
- data: options.data,
97
- message: isTemplate ? undefined : content,
98
- template: isTemplate ? content : undefined,
99
- };
100
- this._toasts.update(ts => [...ts, toast]);
101
- if (toast.duration > 0) {
102
- this.timers.set(id, setTimeout(() => this.dismiss(id), toast.duration));
103
- }
104
- return id;
105
- }
106
-
107
- /** Show a success toast (string shorthand). */
108
- success(message: string, options?: Omit<KjToastOptions, 'variant'>): string {
109
- return this.show(message, { ...options, variant: 'success' });
110
- }
111
- /** Show an error toast (string shorthand). */
112
- error(message: string, options?: Omit<KjToastOptions, 'variant'>): string {
113
- return this.show(message, { ...options, variant: 'destructive' });
114
- }
115
- /** Show a warning toast (string shorthand). */
116
- warning(message: string, options?: Omit<KjToastOptions, 'variant'>): string {
117
- return this.show(message, { ...options, variant: 'warning' });
118
- }
119
- /** Show an informational toast (string shorthand). */
120
- info(message: string, options?: Omit<KjToastOptions, 'variant'>): string {
121
- return this.show(message, { ...options, variant: 'default' });
122
- }
123
-
124
- /** Manually dismiss a toast by id. */
125
- dismiss(id: string): void {
126
- clearTimeout(this.timers.get(id));
127
- this.timers.delete(id);
128
- this._toasts.update(ts => ts.filter(t => t.id !== id));
129
- }
130
-
131
- /** Dismiss every active toast. */
132
- dismissAll(): void {
133
- this.timers.forEach(t => clearTimeout(t));
134
- this.timers.clear();
135
- this._toasts.set([]);
136
- }
137
-
138
- /**
139
- * Builds a template context for a queued toast — used by `[kjToastViewport]` when rendering.
140
- * `ctx.dismiss` is bound to this service.
141
- */
142
- contextFor<T>(toast: KjToastItem<T>): KjToastTemplateContext<T> {
143
- const ctx: KjToastContext<T> = {
144
- id: toast.id,
145
- variant: toast.variant,
146
- message: toast.message,
147
- title: toast.title,
148
- data: toast.data,
149
- dismiss: () => this.dismiss(toast.id),
150
- };
151
- return Object.assign(ctx, { $implicit: ctx });
152
- }
153
- }
@@ -1,95 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
- import { render } from '@testing-library/angular';
3
- import { axe, toHaveNoViolations } from 'jest-axe';
4
- import { KjToast, KjToastViewport } from './toast';
5
- import { KjToastService } from './toast.service';
6
-
7
- expect.extend(toHaveNoViolations);
8
-
9
- describe('KjToast', () => {
10
- it('sets role=status by default', async () => {
11
- const { container } = await render(`<div kjToast>Saved</div>`, { imports: [KjToast] });
12
- expect(container.querySelector('[kjToast]')).toHaveAttribute('role', 'status');
13
- });
14
-
15
- it('sets role=alert for destructive', async () => {
16
- const { container } = await render(
17
- `<div kjToast [kjToastVariant]="'destructive'">Error</div>`,
18
- { imports: [KjToast] },
19
- );
20
- expect(container.querySelector('[kjToast]')).toHaveAttribute('role', 'alert');
21
- });
22
-
23
- it('sets data-variant attribute', async () => {
24
- const { container } = await render(
25
- `<div kjToast [kjToastVariant]="'success'">OK</div>`,
26
- { imports: [KjToast] },
27
- );
28
- expect(container.querySelector('[kjToast]')).toHaveAttribute('data-variant', 'success');
29
- });
30
-
31
- it('passes axe audit', async () => {
32
- const { container } = await render(`<div kjToast>Saved</div>`, { imports: [KjToast] });
33
- expect(await axe(container)).toHaveNoViolations();
34
- });
35
- });
36
-
37
- describe('KjToastViewport', () => {
38
- it('has aria-live=polite', async () => {
39
- const { container } = await render(`<div kjToastViewport></div>`, { imports: [KjToastViewport] });
40
- expect(container.querySelector('[kjToastViewport]')).toHaveAttribute('aria-live', 'polite');
41
- });
42
-
43
- it('has role=region', async () => {
44
- const { container } = await render(`<div kjToastViewport></div>`, { imports: [KjToastViewport] });
45
- expect(container.querySelector('[kjToastViewport]')).toHaveAttribute('role', 'region');
46
- });
47
- });
48
-
49
- describe('KjToastService', () => {
50
- let svc: KjToastService;
51
-
52
- beforeEach(() => {
53
- TestBed.configureTestingModule({});
54
- svc = TestBed.inject(KjToastService);
55
- });
56
-
57
- it('show(message) adds a toast', () => {
58
- expect(svc.toasts().length).toBe(0);
59
- svc.show('Hello', { duration: 0 });
60
- expect(svc.toasts().length).toBe(1);
61
- expect(svc.toasts()[0].message).toBe('Hello');
62
- });
63
-
64
- it('dismiss() removes a toast', () => {
65
- const id = svc.show('Test', { duration: 0 });
66
- svc.dismiss(id);
67
- expect(svc.toasts().length).toBe(0);
68
- });
69
-
70
- it('success() sets variant=success', () => {
71
- svc.success('Done', { duration: 0 });
72
- expect(svc.toasts()[0].variant).toBe('success');
73
- });
74
-
75
- it('error() sets variant=destructive', () => {
76
- svc.error('Fail', { duration: 0 });
77
- expect(svc.toasts()[0].variant).toBe('destructive');
78
- });
79
-
80
- it('dismissAll() clears all toasts', () => {
81
- svc.show('A', { duration: 0 });
82
- svc.show('B', { duration: 0 });
83
- svc.dismissAll();
84
- expect(svc.toasts().length).toBe(0);
85
- });
86
-
87
- it('contextFor() exposes a bound dismiss callback', () => {
88
- const id = svc.show('Hello', { duration: 0 });
89
- const ctx = svc.contextFor(svc.toasts()[0]);
90
- expect(ctx.id).toBe(id);
91
- expect(ctx.message).toBe('Hello');
92
- ctx.dismiss();
93
- expect(svc.toasts().length).toBe(0);
94
- });
95
- });
@@ -1,102 +0,0 @@
1
- import { InjectionToken, Provider } from '@angular/core';
2
- import { KjToastPositionX, KjToastPositionY } from './toast.types';
3
-
4
- /**
5
- * Behaviour and layout defaults for the toast viewport. Strategies are provided
6
- * via DI — both the service (for duration default) and `[kjToastViewport]` (for
7
- * positioning, gap, max visible, expand-on-hover) inject this token.
8
- */
9
- export interface KjToastStrategy {
10
- /** Cap on simultaneously-rendered toasts. Excess stays queued. */
11
- maxVisible: number;
12
- /** Pixel gap between stacked toasts — exposed as `--kj-toast-gap`. */
13
- gap: number;
14
- /** Base `z-index` for stacked toasts — exposed as `--kj-toast-z-index`. */
15
- baseZIndex: number;
16
- /** Horizontal anchor. */
17
- positionX: KjToastPositionX;
18
- /** Vertical anchor. */
19
- positionY: KjToastPositionY;
20
- /** Default auto-dismiss delay in ms. `0` = persistent. */
21
- duration: number;
22
- /** When `true`, the viewport flips `data-expanded` on `mouseenter` / `mouseleave`. */
23
- expandOnHover: boolean;
24
- }
25
-
26
- /**
27
- * Sonner-inspired defaults: max 3 visible, stacked at the bottom-right,
28
- * expand on hover, 4s default duration, 14px gap. Matches the popular
29
- * [Sonner](https://sonner.emilkowal.ski) toast feel.
30
- */
31
- export const KJ_TOAST_SONNER_STRATEGY: KjToastStrategy = Object.freeze({
32
- maxVisible: 3,
33
- gap: 14,
34
- baseZIndex: 100,
35
- positionX: 'end',
36
- positionY: 'bottom',
37
- duration: 4000,
38
- expandOnHover: true,
39
- });
40
-
41
- /**
42
- * Plain vertical list: unlimited visible, no hover expansion, standard 5s duration.
43
- * Closer to Material / Bootstrap-style "stacked notifications" with no fancy stacking.
44
- */
45
- export const KJ_TOAST_LIST_STRATEGY: KjToastStrategy = Object.freeze({
46
- maxVisible: Number.POSITIVE_INFINITY,
47
- gap: 8,
48
- baseZIndex: 100,
49
- positionX: 'end',
50
- positionY: 'bottom',
51
- duration: 5000,
52
- expandOnHover: false,
53
- });
54
-
55
- /** DI token holding the active toast strategy. Defaults to `KJ_TOAST_SONNER_STRATEGY`. */
56
- export const KJ_TOAST_STRATEGY = new InjectionToken<KjToastStrategy>('KjToastStrategy', {
57
- providedIn: 'root',
58
- factory: () => KJ_TOAST_SONNER_STRATEGY,
59
- });
60
-
61
- /**
62
- * Use Sonner-inspired stacking (3 visible, expand on hover, bottom-right).
63
- * Pass `overrides` to tweak any individual property without rewriting the rest.
64
- *
65
- * @example
66
- * ```ts
67
- * bootstrapApplication(AppComponent, {
68
- * providers: [
69
- * provideKjToastSonnerStrategy({ positionY: 'top' }),
70
- * ],
71
- * });
72
- * ```
73
- */
74
- export function provideKjToastSonnerStrategy(overrides?: Partial<KjToastStrategy>): Provider {
75
- return {
76
- provide: KJ_TOAST_STRATEGY,
77
- useValue: { ...KJ_TOAST_SONNER_STRATEGY, ...overrides },
78
- };
79
- }
80
-
81
- /**
82
- * Use a plain vertical list (unlimited, no hover expansion).
83
- *
84
- * @example
85
- * ```ts
86
- * provideKjToastListStrategy({ maxVisible: 5, positionX: 'start' })
87
- * ```
88
- */
89
- export function provideKjToastListStrategy(overrides?: Partial<KjToastStrategy>): Provider {
90
- return {
91
- provide: KJ_TOAST_STRATEGY,
92
- useValue: { ...KJ_TOAST_LIST_STRATEGY, ...overrides },
93
- };
94
- }
95
-
96
- /** Fully-custom strategy override. */
97
- export function provideKjToastStrategy(strategy: KjToastStrategy): Provider {
98
- return {
99
- provide: KJ_TOAST_STRATEGY,
100
- useValue: strategy,
101
- };
102
- }