@nanahoshi/mona-ui 0.13.1 → 0.15.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/chart/README.md +662 -0
- package/chart/package.json +4 -0
- package/fesm2022/nanahoshi-mona-ui-chart.mjs +64226 -0
- package/fesm2022/nanahoshi-mona-ui-spinner.mjs +580 -0
- package/fesm2022/nanahoshi-mona-ui-tabs.mjs +1 -1
- package/package.json +15 -1
- package/spinner/package.json +4 -0
- package/types/nanahoshi-mona-ui-chart.d.ts +7815 -0
- package/types/nanahoshi-mona-ui-spinner.d.ts +114 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { computed, input, Component, output, inject, ApplicationRef, DOCUMENT, Injector, RendererFactory2, createComponent, Injectable } from '@angular/core';
|
|
3
|
+
import { LucideLoader, LucideDynamicIcon } from '@lucide/angular';
|
|
4
|
+
import { classInputToClass } from '@nanahoshi/mona-ui/common';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { cva } from 'class-variance-authority';
|
|
7
|
+
import { Subject, defer, finalize } from 'rxjs';
|
|
8
|
+
import { v4 } from 'uuid';
|
|
9
|
+
|
|
10
|
+
const spinnerThemeVariants = cva("inline-flex items-center justify-center flex-none aspect-square relative text-current select-none", {
|
|
11
|
+
variants: {
|
|
12
|
+
size: {
|
|
13
|
+
small: "w-3 h-3",
|
|
14
|
+
medium: "w-4 h-4",
|
|
15
|
+
large: "w-6 h-6"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
size: "medium"
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @description
|
|
25
|
+
* An indeterminate loading indicator primitive for communicating ongoing activity.
|
|
26
|
+
* Supports multiple appearances, standard sizes, and accessible status announcements.
|
|
27
|
+
*/
|
|
28
|
+
class SpinnerComponent {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.baseClass = computed(() => {
|
|
31
|
+
const variantClass = spinnerThemeVariants({ size: this.size() });
|
|
32
|
+
return twMerge(variantClass, this.userClass());
|
|
33
|
+
}, /* @ts-ignore */
|
|
34
|
+
...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
|
|
35
|
+
this.loaderIcon = LucideLoader;
|
|
36
|
+
this.pixelSize = computed(() => {
|
|
37
|
+
switch (this.size()) {
|
|
38
|
+
case "small":
|
|
39
|
+
return 12;
|
|
40
|
+
case "large":
|
|
41
|
+
return 24;
|
|
42
|
+
case "medium":
|
|
43
|
+
default:
|
|
44
|
+
return 16;
|
|
45
|
+
}
|
|
46
|
+
}, /* @ts-ignore */
|
|
47
|
+
...(ngDevMode ? [{ debugName: "pixelSize" }] : /* istanbul ignore next */ []));
|
|
48
|
+
/**
|
|
49
|
+
* @description Visual appearance of the spinner animation.
|
|
50
|
+
* @default "default"
|
|
51
|
+
*/
|
|
52
|
+
this.appearance = input("default", /* @ts-ignore */
|
|
53
|
+
...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
|
|
54
|
+
/**
|
|
55
|
+
* @description Accessible text announced by assistive technology when the spinner is not decorative.
|
|
56
|
+
* @default "Loading"
|
|
57
|
+
*/
|
|
58
|
+
this.ariaLabel = input("Loading", { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: "aria-label" });
|
|
59
|
+
/**
|
|
60
|
+
* @description When `true`, removes the `status` role and hides the spinner from assistive technology with `aria-hidden="true"`.
|
|
61
|
+
* Useful when a parent element already manages loading semantics (such as `aria-busy="true"` on a button).
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
this.decorative = input(false, /* @ts-ignore */
|
|
65
|
+
...(ngDevMode ? [{ debugName: "decorative" }] : /* istanbul ignore next */ []));
|
|
66
|
+
/**
|
|
67
|
+
* @description Size preset controlling the dimensions of the indicator.
|
|
68
|
+
* @default "medium"
|
|
69
|
+
*/
|
|
70
|
+
this.size = input("medium", /* @ts-ignore */
|
|
71
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
72
|
+
/**
|
|
73
|
+
* @description Additional CSS classes merged onto the host element via `tailwind-merge`.
|
|
74
|
+
* @default ""
|
|
75
|
+
*/
|
|
76
|
+
this.userClass = input("", { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: "class",
|
|
77
|
+
transform: value => classInputToClass(value) });
|
|
78
|
+
}
|
|
79
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
80
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SpinnerComponent, isStandalone: true, selector: "mona-spinner", inputs: { appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, decorative: { classPropertyName: "decorative", publicName: "decorative", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "decorative() ? 'true' : null", "attr.aria-label": "decorative() ? null : (ariaLabel() || 'Loading')", "attr.role": "decorative() ? null : 'status'", "class": "baseClass()" } }, ngImport: i0, template: "@switch (appearance()) {\n @case (\"default\") {\n <svg [lucideIcon]=\"loaderIcon\" [size]=\"pixelSize()\" class=\"animate-[spin_1.5s_linear_infinite] motion-reduce:animate-none\"></svg>\n }\n @case (\"pulsing\") {\n <span class=\"mona-spinner-pulsing-container\">\n <span data-segment class=\"mona-spinner-pulsing-segment mona-spinner-pulsing-1\"></span>\n <span data-segment class=\"mona-spinner-pulsing-segment mona-spinner-pulsing-2\"></span>\n </span>\n }\n @case (\"pulsing-triad\") {\n <span class=\"mona-spinner-pulsing-triad-container\">\n <span data-segment class=\"mona-spinner-pulsing-triad-segment mona-spinner-pulsing-triad-1\"></span>\n <span data-segment class=\"mona-spinner-pulsing-triad-segment mona-spinner-pulsing-triad-2\"></span>\n <span data-segment class=\"mona-spinner-pulsing-triad-segment mona-spinner-pulsing-triad-3\"></span>\n </span>\n }\n @case (\"pulsing-ring\") {\n <span class=\"mona-spinner-pulsing-ring-container\">\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-1\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-2\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-3\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-4\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-5\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-6\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-7\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-8\"></span>\n </span>\n }\n @case (\"converging-spinner\") {\n <span class=\"mona-spinner-converge-container\">\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-1\"></span>\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-2\"></span>\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-3\"></span>\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-4\"></span>\n </span>\n }\n}\n", styles: [":host{display:inline-flex;vertical-align:middle}.mona-spinner-pulsing-container{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.mona-spinner-pulsing-segment{position:absolute;inset:0;border-radius:9999px;background-color:currentColor;opacity:.6;animation:mona-spinner-pulse 1.2s cubic-bezier(0,.2,.8,1) infinite}.mona-spinner-pulsing-2{animation-delay:-.6s}@keyframes mona-spinner-pulse{0%{transform:scale(.1);opacity:.9}50%{transform:scale(.6);opacity:.5}to{transform:scale(1);opacity:0}}.mona-spinner-pulsing-triad-container{position:relative;width:100%;height:100%}.mona-spinner-pulsing-triad-segment{position:absolute;width:32%;height:32%;border-radius:9999px;background-color:currentColor;animation:mona-spinner-dot-pulse 1.4s ease-in-out infinite}.mona-spinner-pulsing-triad-1{top:0;left:34%;animation-delay:-.933s}.mona-spinner-pulsing-triad-2{bottom:0;left:0;animation-delay:-.467s}.mona-spinner-pulsing-triad-3{bottom:0;right:0;animation-delay:0s}@keyframes mona-spinner-dot-pulse{0%,to{transform:scale(.4);opacity:.4}50%{transform:scale(1);opacity:1}}.mona-spinner-pulsing-ring-container{position:relative;width:100%;height:100%}.mona-spinner-pulsing-ring-segment{position:absolute;width:22%;height:22%;border-radius:9999px;background-color:currentColor;animation:mona-spinner-dot-pulse 1.6s ease-in-out infinite}.mona-spinner-pulsing-ring-1{top:5%;left:39%;animation-delay:0s}.mona-spinner-pulsing-ring-2{top:14.96%;left:63.04%;animation-delay:-.2s}.mona-spinner-pulsing-ring-3{top:39%;left:73%;animation-delay:-.4s}.mona-spinner-pulsing-ring-4{top:63.04%;left:63.04%;animation-delay:-.6s}.mona-spinner-pulsing-ring-5{top:73%;left:39%;animation-delay:-.8s}.mona-spinner-pulsing-ring-6{top:63.04%;left:14.96%;animation-delay:-1s}.mona-spinner-pulsing-ring-7{top:39%;left:5%;animation-delay:-1.2s}.mona-spinner-pulsing-ring-8{top:14.96%;left:14.96%;animation-delay:-1.4s}.mona-spinner-converge-container{position:relative;width:100%;height:100%;animation:mona-spinner-rotate 1.5s linear infinite}.mona-spinner-converge-segment{position:absolute;width:32%;height:32%;border-radius:9999px;background-color:currentColor;animation:mona-spinner-converge 1.5s ease-in-out infinite}.mona-spinner-converge-1{top:0;left:0}.mona-spinner-converge-2{top:0;right:0}.mona-spinner-converge-3{bottom:0;right:0}.mona-spinner-converge-4{bottom:0;left:0}@keyframes mona-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes mona-spinner-converge{0%,to{transform:scale(1)}50%{transform:scale(.4)}}@media(prefers-reduced-motion:reduce){.mona-spinner-pulsing-segment,.mona-spinner-pulsing-triad-segment,.mona-spinner-pulsing-ring-segment,.mona-spinner-converge-container,.mona-spinner-converge-segment{animation:none!important}.mona-spinner-pulsing-segment{opacity:.6;transform:scale(.85)}.mona-spinner-pulsing-2{opacity:.3;transform:scale(.5)}.mona-spinner-pulsing-triad-segment,.mona-spinner-pulsing-ring-segment{opacity:.7;transform:scale(.8)}}\n"], dependencies: [{ kind: "component", type: LucideDynamicIcon, selector: "svg[lucideIcon]", inputs: ["lucideIcon"] }] }); }
|
|
81
|
+
}
|
|
82
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerComponent, decorators: [{
|
|
83
|
+
type: Component,
|
|
84
|
+
args: [{ selector: "mona-spinner", imports: [LucideDynamicIcon], host: {
|
|
85
|
+
"[attr.aria-hidden]": "decorative() ? 'true' : null",
|
|
86
|
+
"[attr.aria-label]": "decorative() ? null : (ariaLabel() || 'Loading')",
|
|
87
|
+
"[attr.role]": "decorative() ? null : 'status'",
|
|
88
|
+
"[class]": "baseClass()"
|
|
89
|
+
}, template: "@switch (appearance()) {\n @case (\"default\") {\n <svg [lucideIcon]=\"loaderIcon\" [size]=\"pixelSize()\" class=\"animate-[spin_1.5s_linear_infinite] motion-reduce:animate-none\"></svg>\n }\n @case (\"pulsing\") {\n <span class=\"mona-spinner-pulsing-container\">\n <span data-segment class=\"mona-spinner-pulsing-segment mona-spinner-pulsing-1\"></span>\n <span data-segment class=\"mona-spinner-pulsing-segment mona-spinner-pulsing-2\"></span>\n </span>\n }\n @case (\"pulsing-triad\") {\n <span class=\"mona-spinner-pulsing-triad-container\">\n <span data-segment class=\"mona-spinner-pulsing-triad-segment mona-spinner-pulsing-triad-1\"></span>\n <span data-segment class=\"mona-spinner-pulsing-triad-segment mona-spinner-pulsing-triad-2\"></span>\n <span data-segment class=\"mona-spinner-pulsing-triad-segment mona-spinner-pulsing-triad-3\"></span>\n </span>\n }\n @case (\"pulsing-ring\") {\n <span class=\"mona-spinner-pulsing-ring-container\">\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-1\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-2\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-3\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-4\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-5\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-6\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-7\"></span>\n <span data-segment class=\"mona-spinner-pulsing-ring-segment mona-spinner-pulsing-ring-8\"></span>\n </span>\n }\n @case (\"converging-spinner\") {\n <span class=\"mona-spinner-converge-container\">\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-1\"></span>\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-2\"></span>\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-3\"></span>\n <span data-segment class=\"mona-spinner-converge-segment mona-spinner-converge-4\"></span>\n </span>\n }\n}\n", styles: [":host{display:inline-flex;vertical-align:middle}.mona-spinner-pulsing-container{position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.mona-spinner-pulsing-segment{position:absolute;inset:0;border-radius:9999px;background-color:currentColor;opacity:.6;animation:mona-spinner-pulse 1.2s cubic-bezier(0,.2,.8,1) infinite}.mona-spinner-pulsing-2{animation-delay:-.6s}@keyframes mona-spinner-pulse{0%{transform:scale(.1);opacity:.9}50%{transform:scale(.6);opacity:.5}to{transform:scale(1);opacity:0}}.mona-spinner-pulsing-triad-container{position:relative;width:100%;height:100%}.mona-spinner-pulsing-triad-segment{position:absolute;width:32%;height:32%;border-radius:9999px;background-color:currentColor;animation:mona-spinner-dot-pulse 1.4s ease-in-out infinite}.mona-spinner-pulsing-triad-1{top:0;left:34%;animation-delay:-.933s}.mona-spinner-pulsing-triad-2{bottom:0;left:0;animation-delay:-.467s}.mona-spinner-pulsing-triad-3{bottom:0;right:0;animation-delay:0s}@keyframes mona-spinner-dot-pulse{0%,to{transform:scale(.4);opacity:.4}50%{transform:scale(1);opacity:1}}.mona-spinner-pulsing-ring-container{position:relative;width:100%;height:100%}.mona-spinner-pulsing-ring-segment{position:absolute;width:22%;height:22%;border-radius:9999px;background-color:currentColor;animation:mona-spinner-dot-pulse 1.6s ease-in-out infinite}.mona-spinner-pulsing-ring-1{top:5%;left:39%;animation-delay:0s}.mona-spinner-pulsing-ring-2{top:14.96%;left:63.04%;animation-delay:-.2s}.mona-spinner-pulsing-ring-3{top:39%;left:73%;animation-delay:-.4s}.mona-spinner-pulsing-ring-4{top:63.04%;left:63.04%;animation-delay:-.6s}.mona-spinner-pulsing-ring-5{top:73%;left:39%;animation-delay:-.8s}.mona-spinner-pulsing-ring-6{top:63.04%;left:14.96%;animation-delay:-1s}.mona-spinner-pulsing-ring-7{top:39%;left:5%;animation-delay:-1.2s}.mona-spinner-pulsing-ring-8{top:14.96%;left:14.96%;animation-delay:-1.4s}.mona-spinner-converge-container{position:relative;width:100%;height:100%;animation:mona-spinner-rotate 1.5s linear infinite}.mona-spinner-converge-segment{position:absolute;width:32%;height:32%;border-radius:9999px;background-color:currentColor;animation:mona-spinner-converge 1.5s ease-in-out infinite}.mona-spinner-converge-1{top:0;left:0}.mona-spinner-converge-2{top:0;right:0}.mona-spinner-converge-3{bottom:0;right:0}.mona-spinner-converge-4{bottom:0;left:0}@keyframes mona-spinner-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes mona-spinner-converge{0%,to{transform:scale(1)}50%{transform:scale(.4)}}@media(prefers-reduced-motion:reduce){.mona-spinner-pulsing-segment,.mona-spinner-pulsing-triad-segment,.mona-spinner-pulsing-ring-segment,.mona-spinner-converge-container,.mona-spinner-converge-segment{animation:none!important}.mona-spinner-pulsing-segment{opacity:.6;transform:scale(.85)}.mona-spinner-pulsing-2{opacity:.3;transform:scale(.5)}.mona-spinner-pulsing-triad-segment,.mona-spinner-pulsing-ring-segment{opacity:.7;transform:scale(.8)}}\n"] }]
|
|
90
|
+
}], propDecorators: { appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], decorative: [{ type: i0.Input, args: [{ isSignal: true, alias: "decorative", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
|
|
91
|
+
|
|
92
|
+
const spinnerOverlayThemeVariants = cva(`
|
|
93
|
+
isolate flex items-center justify-center
|
|
94
|
+
pointer-events-auto select-none
|
|
95
|
+
bg-background/60 backdrop-blur-xs
|
|
96
|
+
text-foreground
|
|
97
|
+
`, {
|
|
98
|
+
variants: {
|
|
99
|
+
fullPage: {
|
|
100
|
+
true: "fixed inset-0 z-50",
|
|
101
|
+
false: "absolute inset-0 z-1"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
defaultVariants: {
|
|
105
|
+
fullPage: false
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @description
|
|
111
|
+
* Internal overlay component managed exclusively by `SpinnerService`.
|
|
112
|
+
* Hosts a decorative `SpinnerComponent`, status message, and optional cancellation control.
|
|
113
|
+
*/
|
|
114
|
+
class SpinnerOverlayComponent {
|
|
115
|
+
constructor() {
|
|
116
|
+
this.baseClass = computed(() => {
|
|
117
|
+
return spinnerOverlayThemeVariants({ fullPage: this.fullPage() });
|
|
118
|
+
}, /* @ts-ignore */
|
|
119
|
+
...(ngDevMode ? [{ debugName: "baseClass" }] : /* istanbul ignore next */ []));
|
|
120
|
+
this.effectiveZIndex = computed(() => {
|
|
121
|
+
const customZIndex = this.zIndex();
|
|
122
|
+
if (customZIndex !== undefined) {
|
|
123
|
+
return customZIndex;
|
|
124
|
+
}
|
|
125
|
+
return this.fullPage() ? 50 : 1;
|
|
126
|
+
}, /* @ts-ignore */
|
|
127
|
+
...(ngDevMode ? [{ debugName: "effectiveZIndex" }] : /* istanbul ignore next */ []));
|
|
128
|
+
/**
|
|
129
|
+
* @description Visual appearance of the nested spinner animation.
|
|
130
|
+
* @default "default"
|
|
131
|
+
*/
|
|
132
|
+
this.appearance = input("default", /* @ts-ignore */
|
|
133
|
+
...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
|
|
134
|
+
/**
|
|
135
|
+
* @description Emits when the consumer clicks the cancellation action button.
|
|
136
|
+
*/
|
|
137
|
+
this.cancel = output();
|
|
138
|
+
/**
|
|
139
|
+
* @description Whether the cancellation button is displayed in the overlay.
|
|
140
|
+
* @default false
|
|
141
|
+
*/
|
|
142
|
+
this.cancellable = input(false, /* @ts-ignore */
|
|
143
|
+
...(ngDevMode ? [{ debugName: "cancellable" }] : /* istanbul ignore next */ []));
|
|
144
|
+
/**
|
|
145
|
+
* @description Label text displayed inside the cancellation button.
|
|
146
|
+
* @default "Cancel"
|
|
147
|
+
*/
|
|
148
|
+
this.cancelText = input("Cancel", /* @ts-ignore */
|
|
149
|
+
...(ngDevMode ? [{ debugName: "cancelText" }] : /* istanbul ignore next */ []));
|
|
150
|
+
/**
|
|
151
|
+
* @description Whether the overlay is fixed across the viewport or absolute within a target container.
|
|
152
|
+
* @default false
|
|
153
|
+
*/
|
|
154
|
+
this.fullPage = input(false, /* @ts-ignore */
|
|
155
|
+
...(ngDevMode ? [{ debugName: "fullPage" }] : /* istanbul ignore next */ []));
|
|
156
|
+
/**
|
|
157
|
+
* @description Size preset passed to the nested spinner.
|
|
158
|
+
* @default "medium"
|
|
159
|
+
*/
|
|
160
|
+
this.size = input("medium", /* @ts-ignore */
|
|
161
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
162
|
+
/**
|
|
163
|
+
* @description Status text displayed below the spinner indicator.
|
|
164
|
+
* @default undefined
|
|
165
|
+
*/
|
|
166
|
+
this.text = input(undefined, /* @ts-ignore */
|
|
167
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
168
|
+
/**
|
|
169
|
+
* @description Custom z-index value for unusual stacking contexts.
|
|
170
|
+
* @default undefined
|
|
171
|
+
*/
|
|
172
|
+
this.zIndex = input(undefined, /* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "zIndex" }] : /* istanbul ignore next */ []));
|
|
174
|
+
}
|
|
175
|
+
onCancel() {
|
|
176
|
+
this.cancel.emit();
|
|
177
|
+
}
|
|
178
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
179
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SpinnerOverlayComponent, isStandalone: true, selector: "mona-spinner-overlay", inputs: { appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, cancellable: { classPropertyName: "cancellable", publicName: "cancellable", isSignal: true, isRequired: false, transformFunction: null }, cancelText: { classPropertyName: "cancelText", publicName: "cancelText", isSignal: true, isRequired: false, transformFunction: null }, fullPage: { classPropertyName: "fullPage", publicName: "fullPage", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancel: "cancel" }, host: { properties: { "class": "baseClass()", "style.z-index": "effectiveZIndex()" } }, ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center gap-2.5 p-4 text-sm font-medium text-foreground text-center\" role=\"status\" aria-live=\"polite\">\n <mona-spinner [appearance]=\"appearance()\" [size]=\"size()\" [decorative]=\"true\"></mona-spinner>\n\n @if (text()) {\n <span class=\"max-w-xs truncate\">{{ text() }}</span>\n } @else {\n <span class=\"sr-only\">Loading</span>\n }\n\n @if (cancellable()) {\n <button\n type=\"button\"\n (click)=\"onCancel()\"\n class=\"mt-1 inline-flex items-center justify-center rounded-md border border-border bg-background px-2.5 py-1 text-xs font-medium text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer transition-colors\"\n >\n {{ cancelText() }}\n </button>\n }\n</div>\n", dependencies: [{ kind: "component", type: SpinnerComponent, selector: "mona-spinner", inputs: ["appearance", "aria-label", "decorative", "size", "class"] }] }); }
|
|
180
|
+
}
|
|
181
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerOverlayComponent, decorators: [{
|
|
182
|
+
type: Component,
|
|
183
|
+
args: [{ selector: "mona-spinner-overlay", imports: [SpinnerComponent], host: {
|
|
184
|
+
"[class]": "baseClass()",
|
|
185
|
+
"[style.z-index]": "effectiveZIndex()"
|
|
186
|
+
}, template: "<div class=\"flex flex-col items-center justify-center gap-2.5 p-4 text-sm font-medium text-foreground text-center\" role=\"status\" aria-live=\"polite\">\n <mona-spinner [appearance]=\"appearance()\" [size]=\"size()\" [decorative]=\"true\"></mona-spinner>\n\n @if (text()) {\n <span class=\"max-w-xs truncate\">{{ text() }}</span>\n } @else {\n <span class=\"sr-only\">Loading</span>\n }\n\n @if (cancellable()) {\n <button\n type=\"button\"\n (click)=\"onCancel()\"\n class=\"mt-1 inline-flex items-center justify-center rounded-md border border-border bg-background px-2.5 py-1 text-xs font-medium text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer transition-colors\"\n >\n {{ cancelText() }}\n </button>\n }\n</div>\n" }]
|
|
187
|
+
}], propDecorators: { appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], cancel: [{ type: i0.Output, args: ["cancel"] }], cancellable: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancellable", required: false }] }], cancelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelText", required: false }] }], fullPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullPage", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], zIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "zIndex", required: false }] }] } });
|
|
188
|
+
|
|
189
|
+
class SpinnerService {
|
|
190
|
+
#appRef = inject(ApplicationRef);
|
|
191
|
+
#document = inject(DOCUMENT);
|
|
192
|
+
#injector = inject(Injector);
|
|
193
|
+
#renderer = inject(RendererFactory2).createRenderer(null, null);
|
|
194
|
+
#requests = new Map();
|
|
195
|
+
#targetStates = new Set();
|
|
196
|
+
#targets = new WeakMap();
|
|
197
|
+
/**
|
|
198
|
+
* Hides and destroys the spinner associated with the given `SpinnerRef` or string `id`.
|
|
199
|
+
*/
|
|
200
|
+
hide(refOrId) {
|
|
201
|
+
const id = typeof refOrId === "string" ? refOrId : refOrId.id;
|
|
202
|
+
this.#closeRequest(id);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Tears down every active/pending spinner request and physical overlay so
|
|
206
|
+
* nothing is left behind if the service outlives its consumers.
|
|
207
|
+
*/
|
|
208
|
+
ngOnDestroy() {
|
|
209
|
+
for (const request of this.#requests.values()) {
|
|
210
|
+
if (request.delayTimer != null) {
|
|
211
|
+
clearTimeout(request.delayTimer);
|
|
212
|
+
request.delayTimer = null;
|
|
213
|
+
}
|
|
214
|
+
if (!request.cancellationHandled) {
|
|
215
|
+
request.cancelledSubject.complete();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
this.#requests.clear();
|
|
219
|
+
for (const targetState of this.#targetStates) {
|
|
220
|
+
if (targetState.minDurationTimer != null) {
|
|
221
|
+
clearTimeout(targetState.minDurationTimer);
|
|
222
|
+
targetState.minDurationTimer = null;
|
|
223
|
+
}
|
|
224
|
+
targetState.cancelSubscription?.unsubscribe();
|
|
225
|
+
targetState.cancelSubscription = null;
|
|
226
|
+
if (targetState.componentRef != null) {
|
|
227
|
+
this.#appRef.detachView(targetState.componentRef.hostView);
|
|
228
|
+
targetState.componentRef.destroy();
|
|
229
|
+
targetState.componentRef = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
this.#targetStates.clear();
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Displays a spinner loading overlay according to the provided options.
|
|
236
|
+
* Returns a `SpinnerRef` handle for lifecycle management and updates.
|
|
237
|
+
*/
|
|
238
|
+
show(options) {
|
|
239
|
+
const id = options?.id ?? `spinner-${v4()}`;
|
|
240
|
+
// If an existing request with the same ID exists, close it cleanly first
|
|
241
|
+
if (this.#requests.has(id)) {
|
|
242
|
+
this.#closeRequest(id);
|
|
243
|
+
}
|
|
244
|
+
const targets = this.#normalizeTargets(options?.target);
|
|
245
|
+
const isFullPage = !options?.target || targets.length === 0;
|
|
246
|
+
const resolvedTargets = isFullPage ? [this.#document.body] : targets;
|
|
247
|
+
const cancellableOpt = options?.cancellable;
|
|
248
|
+
const isCancellable = !!cancellableOpt;
|
|
249
|
+
const cancelText = typeof cancellableOpt === "object" && cancellableOpt.text ? cancellableOpt.text : "Cancel";
|
|
250
|
+
const onCancel = typeof cancellableOpt === "object" ? cancellableOpt.onCancel : undefined;
|
|
251
|
+
const cancelledSubject = new Subject();
|
|
252
|
+
const request = {
|
|
253
|
+
activatedAt: Date.now(),
|
|
254
|
+
appearance: options?.appearance ?? "default",
|
|
255
|
+
cancellationHandled: false,
|
|
256
|
+
cancellable: isCancellable,
|
|
257
|
+
cancelText,
|
|
258
|
+
delayTimer: null,
|
|
259
|
+
id,
|
|
260
|
+
isFullPage,
|
|
261
|
+
onCancel,
|
|
262
|
+
options: options ?? {},
|
|
263
|
+
registeredAt: Date.now(),
|
|
264
|
+
size: options?.size ?? "medium",
|
|
265
|
+
status: "PENDING",
|
|
266
|
+
cancelledSubject,
|
|
267
|
+
targets: resolvedTargets,
|
|
268
|
+
text: options?.text
|
|
269
|
+
};
|
|
270
|
+
this.#requests.set(id, request);
|
|
271
|
+
// Register request against all targets and set aria-busy. Targets that already
|
|
272
|
+
// have a visible overlay activate immediately; idle targets respect `delay`.
|
|
273
|
+
const immediateTargets = [];
|
|
274
|
+
const delayedTargets = [];
|
|
275
|
+
for (const target of resolvedTargets) {
|
|
276
|
+
const targetState = this.#getOrCreateTargetState(target, isFullPage);
|
|
277
|
+
targetState.pendingRequestIds.add(id);
|
|
278
|
+
if (targetState.componentRef != null) {
|
|
279
|
+
immediateTargets.push(target);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
delayedTargets.push(target);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (immediateTargets.length > 0) {
|
|
286
|
+
this.#activateRequestOnTargets(request, immediateTargets);
|
|
287
|
+
}
|
|
288
|
+
const delay = options?.delay ?? 0;
|
|
289
|
+
if (delayedTargets.length > 0) {
|
|
290
|
+
if (delay > 0) {
|
|
291
|
+
request.delayTimer = setTimeout(() => {
|
|
292
|
+
request.delayTimer = null;
|
|
293
|
+
if (request.status !== "CLOSED") {
|
|
294
|
+
this.#activateRequestOnTargets(request, delayedTargets);
|
|
295
|
+
}
|
|
296
|
+
}, delay);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
this.#activateRequestOnTargets(request, delayedTargets);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
id,
|
|
304
|
+
cancelled$: cancelledSubject.asObservable(),
|
|
305
|
+
close: () => this.#closeRequest(id),
|
|
306
|
+
update: update => this.#updateRequest(id, update)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Wraps an Observable operation with a Spinner lifecycle.
|
|
311
|
+
* Shows the spinner on subscription and closes it when the observable completes, errors, or is unsubscribed.
|
|
312
|
+
*/
|
|
313
|
+
track(source, options) {
|
|
314
|
+
return defer(() => {
|
|
315
|
+
const ref = this.show(options);
|
|
316
|
+
return source.pipe(finalize(() => ref.close()));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
#activateRequestOnTargets(request, targets) {
|
|
320
|
+
if (request.status === "CLOSED") {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
request.status = "VISIBLE";
|
|
324
|
+
request.activatedAt = Date.now();
|
|
325
|
+
for (const target of targets) {
|
|
326
|
+
const targetState = this.#getOrCreateTargetState(target, request.isFullPage);
|
|
327
|
+
targetState.activeRequestIds.add(request.id);
|
|
328
|
+
this.#updateTargetOverlay(targetState);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
#cancelRequest(requestId) {
|
|
332
|
+
const request = this.#requests.get(requestId);
|
|
333
|
+
if (!request || request.cancellationHandled || request.status === "CLOSED") {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
request.cancellationHandled = true;
|
|
337
|
+
request.cancelledSubject.next();
|
|
338
|
+
request.cancelledSubject.complete();
|
|
339
|
+
try {
|
|
340
|
+
if (request.onCancel) {
|
|
341
|
+
request.onCancel();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
this.#closeRequest(requestId);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
#closeRequest(requestId) {
|
|
349
|
+
const request = this.#requests.get(requestId);
|
|
350
|
+
if (!request || request.status === "CLOSED") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
request.status = "CLOSED";
|
|
354
|
+
if (request.delayTimer != null) {
|
|
355
|
+
clearTimeout(request.delayTimer);
|
|
356
|
+
request.delayTimer = null;
|
|
357
|
+
}
|
|
358
|
+
if (!request.cancellationHandled) {
|
|
359
|
+
request.cancelledSubject.complete();
|
|
360
|
+
}
|
|
361
|
+
this.#requests.delete(requestId);
|
|
362
|
+
for (const target of request.targets) {
|
|
363
|
+
const targetState = this.#targets.get(target);
|
|
364
|
+
if (!targetState) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
targetState.pendingRequestIds.delete(requestId);
|
|
368
|
+
targetState.activeRequestIds.delete(requestId);
|
|
369
|
+
if (targetState.activeRequestIds.size > 0) {
|
|
370
|
+
// Other active requests remain; recompute winning presentation
|
|
371
|
+
this.#updateTargetOverlay(targetState);
|
|
372
|
+
}
|
|
373
|
+
else if (targetState.pendingRequestIds.size === 0) {
|
|
374
|
+
// No active or pending requests remain on this target
|
|
375
|
+
const minDuration = request.options.minimumVisibleDuration ?? 0;
|
|
376
|
+
if (minDuration > 0 && targetState.visibleSince != null && targetState.componentRef != null) {
|
|
377
|
+
const elapsed = Date.now() - targetState.visibleSince;
|
|
378
|
+
const remaining = minDuration - elapsed;
|
|
379
|
+
if (remaining > 0) {
|
|
380
|
+
if (targetState.minDurationTimer != null) {
|
|
381
|
+
clearTimeout(targetState.minDurationTimer);
|
|
382
|
+
}
|
|
383
|
+
targetState.minDurationTimer = setTimeout(() => {
|
|
384
|
+
targetState.minDurationTimer = null;
|
|
385
|
+
if (targetState.activeRequestIds.size === 0 && targetState.pendingRequestIds.size === 0) {
|
|
386
|
+
this.#teardownTarget(targetState);
|
|
387
|
+
}
|
|
388
|
+
}, remaining);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
this.#teardownTarget(targetState);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
this.#teardownTarget(targetState);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
#getOrCreateTargetState(target, isFullPage) {
|
|
401
|
+
let state = this.#targets.get(target);
|
|
402
|
+
if (!state) {
|
|
403
|
+
const originalAriaBusy = target.getAttribute("aria-busy");
|
|
404
|
+
const originalInlinePosition = target.style.position;
|
|
405
|
+
state = {
|
|
406
|
+
activeRequestIds: new Set(),
|
|
407
|
+
cancelSubscription: null,
|
|
408
|
+
changedPosition: false,
|
|
409
|
+
componentRef: null,
|
|
410
|
+
isFullPage,
|
|
411
|
+
minDurationTimer: null,
|
|
412
|
+
originalAriaBusy,
|
|
413
|
+
originalInlinePosition,
|
|
414
|
+
pendingRequestIds: new Set(),
|
|
415
|
+
target
|
|
416
|
+
};
|
|
417
|
+
this.#targets.set(target, state);
|
|
418
|
+
this.#targetStates.add(state);
|
|
419
|
+
}
|
|
420
|
+
// Apply aria-busy immediately while logically busy
|
|
421
|
+
this.#renderer.setAttribute(target, "aria-busy", "true");
|
|
422
|
+
// If a delayed teardown was pending, cancel it since new activity arrived
|
|
423
|
+
if (state.minDurationTimer != null) {
|
|
424
|
+
clearTimeout(state.minDurationTimer);
|
|
425
|
+
state.minDurationTimer = null;
|
|
426
|
+
}
|
|
427
|
+
return state;
|
|
428
|
+
}
|
|
429
|
+
#getWinningRequestForTarget(targetState) {
|
|
430
|
+
let winningRequest = null;
|
|
431
|
+
for (const requestId of targetState.activeRequestIds) {
|
|
432
|
+
const req = this.#requests.get(requestId);
|
|
433
|
+
if (req && req.status === "VISIBLE") {
|
|
434
|
+
if (!winningRequest || req.activatedAt >= winningRequest.activatedAt) {
|
|
435
|
+
winningRequest = req;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return winningRequest;
|
|
440
|
+
}
|
|
441
|
+
#normalizeTargets(target) {
|
|
442
|
+
if (!target) {
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
445
|
+
const rawList = Array.isArray(target) ? target : [target];
|
|
446
|
+
const result = [];
|
|
447
|
+
for (const item of rawList) {
|
|
448
|
+
if (item instanceof HTMLElement) {
|
|
449
|
+
result.push(item);
|
|
450
|
+
}
|
|
451
|
+
else if (item && typeof item === "object" && "nativeElement" in item && item.nativeElement instanceof HTMLElement) {
|
|
452
|
+
result.push(item.nativeElement);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
#teardownTarget(targetState) {
|
|
458
|
+
if (targetState.minDurationTimer != null) {
|
|
459
|
+
clearTimeout(targetState.minDurationTimer);
|
|
460
|
+
targetState.minDurationTimer = null;
|
|
461
|
+
}
|
|
462
|
+
targetState.cancelSubscription?.unsubscribe();
|
|
463
|
+
targetState.cancelSubscription = null;
|
|
464
|
+
if (targetState.componentRef != null) {
|
|
465
|
+
this.#appRef.detachView(targetState.componentRef.hostView);
|
|
466
|
+
targetState.componentRef.destroy();
|
|
467
|
+
targetState.componentRef = null;
|
|
468
|
+
}
|
|
469
|
+
targetState.visibleSince = undefined;
|
|
470
|
+
// Restore aria-busy attribute exactly
|
|
471
|
+
if (targetState.originalAriaBusy === null) {
|
|
472
|
+
this.#renderer.removeAttribute(targetState.target, "aria-busy");
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
this.#renderer.setAttribute(targetState.target, "aria-busy", targetState.originalAriaBusy);
|
|
476
|
+
}
|
|
477
|
+
// Restore target positioning safely
|
|
478
|
+
if (targetState.changedPosition) {
|
|
479
|
+
if (targetState.target.style.position === "relative") {
|
|
480
|
+
this.#renderer.setStyle(targetState.target, "position", targetState.originalInlinePosition || null);
|
|
481
|
+
}
|
|
482
|
+
targetState.changedPosition = false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
#updateRequest(requestId, update) {
|
|
486
|
+
const request = this.#requests.get(requestId);
|
|
487
|
+
if (!request || request.status === "CLOSED") {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (update.appearance !== undefined) {
|
|
491
|
+
request.appearance = update.appearance;
|
|
492
|
+
}
|
|
493
|
+
if (update.size !== undefined) {
|
|
494
|
+
request.size = update.size;
|
|
495
|
+
}
|
|
496
|
+
if (update.text !== undefined) {
|
|
497
|
+
request.text = update.text;
|
|
498
|
+
}
|
|
499
|
+
for (const target of request.targets) {
|
|
500
|
+
const targetState = this.#targets.get(target);
|
|
501
|
+
if (targetState) {
|
|
502
|
+
this.#updateTargetOverlay(targetState);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
#updateTargetOverlay(targetState) {
|
|
507
|
+
const winner = this.#getWinningRequestForTarget(targetState);
|
|
508
|
+
if (!winner) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (!targetState.componentRef) {
|
|
512
|
+
// Need containing block for absolute local positioning
|
|
513
|
+
if (!targetState.isFullPage) {
|
|
514
|
+
const defaultView = this.#document.defaultView;
|
|
515
|
+
const computedPosition = defaultView ? defaultView.getComputedStyle(targetState.target).position : "static";
|
|
516
|
+
if (computedPosition === "static") {
|
|
517
|
+
this.#renderer.setStyle(targetState.target, "position", "relative");
|
|
518
|
+
targetState.changedPosition = true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const compRef = createComponent(SpinnerOverlayComponent, {
|
|
522
|
+
environmentInjector: this.#appRef.injector,
|
|
523
|
+
elementInjector: this.#injector
|
|
524
|
+
});
|
|
525
|
+
compRef.setInput("appearance", winner.appearance);
|
|
526
|
+
compRef.setInput("size", winner.size);
|
|
527
|
+
compRef.setInput("text", winner.text);
|
|
528
|
+
compRef.setInput("cancellable", winner.cancellable);
|
|
529
|
+
compRef.setInput("cancelText", winner.cancelText);
|
|
530
|
+
compRef.setInput("fullPage", targetState.isFullPage);
|
|
531
|
+
if (winner.options.zIndex !== undefined) {
|
|
532
|
+
compRef.setInput("zIndex", winner.options.zIndex);
|
|
533
|
+
}
|
|
534
|
+
targetState.cancelSubscription = compRef.instance.cancel.subscribe(() => {
|
|
535
|
+
const currentWinner = this.#getWinningRequestForTarget(targetState);
|
|
536
|
+
if (currentWinner) {
|
|
537
|
+
this.#cancelRequest(currentWinner.id);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
this.#appRef.attachView(compRef.hostView);
|
|
541
|
+
this.#renderer.appendChild(targetState.target, compRef.location.nativeElement);
|
|
542
|
+
compRef.changeDetectorRef.detectChanges();
|
|
543
|
+
targetState.componentRef = compRef;
|
|
544
|
+
targetState.visibleSince = Date.now();
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
// Physical overlay already exists: update its inputs dynamically
|
|
548
|
+
const compRef = targetState.componentRef;
|
|
549
|
+
compRef.setInput("appearance", winner.appearance);
|
|
550
|
+
compRef.setInput("size", winner.size);
|
|
551
|
+
compRef.setInput("text", winner.text);
|
|
552
|
+
compRef.setInput("cancellable", winner.cancellable);
|
|
553
|
+
compRef.setInput("cancelText", winner.cancelText);
|
|
554
|
+
compRef.setInput("fullPage", targetState.isFullPage);
|
|
555
|
+
if (winner.options.zIndex !== undefined) {
|
|
556
|
+
compRef.setInput("zIndex", winner.options.zIndex);
|
|
557
|
+
}
|
|
558
|
+
compRef.changeDetectorRef.detectChanges();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
562
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerService, providedIn: "root" }); }
|
|
563
|
+
}
|
|
564
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SpinnerService, decorators: [{
|
|
565
|
+
type: Injectable,
|
|
566
|
+
args: [{
|
|
567
|
+
providedIn: "root"
|
|
568
|
+
}]
|
|
569
|
+
}] });
|
|
570
|
+
|
|
571
|
+
/*
|
|
572
|
+
* Public API Surface of @nanahoshi/mona-ui/spinner
|
|
573
|
+
*/
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Generated bundle index. Do not edit.
|
|
577
|
+
*/
|
|
578
|
+
|
|
579
|
+
export { SpinnerComponent, SpinnerService };
|
|
580
|
+
//# sourceMappingURL=nanahoshi-mona-ui-spinner.mjs.map
|
|
@@ -260,6 +260,7 @@ class TabListComponent {
|
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
});
|
|
263
|
+
this.setupScrolling();
|
|
263
264
|
afterNextRender({
|
|
264
265
|
write: () => {
|
|
265
266
|
const selectedTab = firstOrDefault(this.tabList(), t => t.id === this.selectedTabId());
|
|
@@ -270,7 +271,6 @@ class TabListComponent {
|
|
|
270
271
|
read: () => {
|
|
271
272
|
this.setupKeyboardNavigation();
|
|
272
273
|
this.setupResizeObserver();
|
|
273
|
-
this.setupScrolling();
|
|
274
274
|
}
|
|
275
275
|
});
|
|
276
276
|
inject(DestroyRef).onDestroy(() => this.#resizeObserver?.disconnect());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanahoshi/mona-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "A very light Angular UI framework",
|
|
5
5
|
"author": "Nanahoshi",
|
|
6
6
|
"repository": {
|
|
@@ -45,8 +45,14 @@
|
|
|
45
45
|
"@mirei/ts-collections": "^17.5.6",
|
|
46
46
|
"class-variance-authority": "^0.7.1",
|
|
47
47
|
"culori": "^4.0.2",
|
|
48
|
+
"d3-hierarchy": "^3.1.2",
|
|
49
|
+
"d3-scale": "^4.0.2",
|
|
50
|
+
"d3-shape": "^3.2.0",
|
|
51
|
+
"html2canvas-pro": "^2.3.9",
|
|
52
|
+
"jspdf": "^4.2.1",
|
|
48
53
|
"luxon": "^3.7.2",
|
|
49
54
|
"remixicon": "^4.9.1",
|
|
55
|
+
"svg2pdf.js": "^2.7.0",
|
|
50
56
|
"tailwind-merge": "^3.6.0",
|
|
51
57
|
"tslib": "^2.8.1",
|
|
52
58
|
"uuid": "^14.0.0"
|
|
@@ -89,6 +95,10 @@
|
|
|
89
95
|
"types": "./types/nanahoshi-mona-ui-card.d.ts",
|
|
90
96
|
"default": "./fesm2022/nanahoshi-mona-ui-card.mjs"
|
|
91
97
|
},
|
|
98
|
+
"./chart": {
|
|
99
|
+
"types": "./types/nanahoshi-mona-ui-chart.d.ts",
|
|
100
|
+
"default": "./fesm2022/nanahoshi-mona-ui-chart.mjs"
|
|
101
|
+
},
|
|
92
102
|
"./check-box": {
|
|
93
103
|
"types": "./types/nanahoshi-mona-ui-check-box.d.ts",
|
|
94
104
|
"default": "./fesm2022/nanahoshi-mona-ui-check-box.mjs"
|
|
@@ -289,6 +299,10 @@
|
|
|
289
299
|
"types": "./types/nanahoshi-mona-ui-slider.d.ts",
|
|
290
300
|
"default": "./fesm2022/nanahoshi-mona-ui-slider.mjs"
|
|
291
301
|
},
|
|
302
|
+
"./spinner": {
|
|
303
|
+
"types": "./types/nanahoshi-mona-ui-spinner.d.ts",
|
|
304
|
+
"default": "./fesm2022/nanahoshi-mona-ui-spinner.mjs"
|
|
305
|
+
},
|
|
292
306
|
"./split-button": {
|
|
293
307
|
"types": "./types/nanahoshi-mona-ui-split-button.d.ts",
|
|
294
308
|
"default": "./fesm2022/nanahoshi-mona-ui-split-button.mjs"
|