@blackcube/aurelia2-bleet 1.0.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/blackcube-aurelia2-bleet-1.0.0.tgz +0 -0
- package/dist/index.es.js +4514 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +4549 -0
- package/dist/index.js.map +1 -0
- package/dist/types/attributes/ajaxify-trigger.d.ts +36 -0
- package/dist/types/attributes/ajaxify-trigger.d.ts.map +1 -0
- package/dist/types/attributes/alert.d.ts +15 -0
- package/dist/types/attributes/alert.d.ts.map +1 -0
- package/dist/types/attributes/badge.d.ts +13 -0
- package/dist/types/attributes/badge.d.ts.map +1 -0
- package/dist/types/attributes/burger.d.ts +11 -0
- package/dist/types/attributes/burger.d.ts.map +1 -0
- package/dist/types/attributes/drawer-trigger.d.ts +16 -0
- package/dist/types/attributes/drawer-trigger.d.ts.map +1 -0
- package/dist/types/attributes/dropdown.d.ts +38 -0
- package/dist/types/attributes/dropdown.d.ts.map +1 -0
- package/dist/types/attributes/index.d.ts +16 -0
- package/dist/types/attributes/index.d.ts.map +1 -0
- package/dist/types/attributes/menu.d.ts +32 -0
- package/dist/types/attributes/menu.d.ts.map +1 -0
- package/dist/types/attributes/modal-trigger.d.ts +16 -0
- package/dist/types/attributes/modal-trigger.d.ts.map +1 -0
- package/dist/types/attributes/pager.d.ts +13 -0
- package/dist/types/attributes/pager.d.ts.map +1 -0
- package/dist/types/attributes/password.d.ts +15 -0
- package/dist/types/attributes/password.d.ts.map +1 -0
- package/dist/types/attributes/profile.d.ts +24 -0
- package/dist/types/attributes/profile.d.ts.map +1 -0
- package/dist/types/attributes/select.d.ts +24 -0
- package/dist/types/attributes/select.d.ts.map +1 -0
- package/dist/types/attributes/tabs.d.ts +16 -0
- package/dist/types/attributes/tabs.d.ts.map +1 -0
- package/dist/types/attributes/toaster-trigger.d.ts +19 -0
- package/dist/types/attributes/toaster-trigger.d.ts.map +1 -0
- package/dist/types/attributes/upload.d.ts +57 -0
- package/dist/types/attributes/upload.d.ts.map +1 -0
- package/dist/types/codecs/ajaxify-codec.d.ts +5 -0
- package/dist/types/codecs/ajaxify-codec.d.ts.map +1 -0
- package/dist/types/codecs/csrf-codec.d.ts +7 -0
- package/dist/types/codecs/csrf-codec.d.ts.map +1 -0
- package/dist/types/codecs/request-codec.d.ts +5 -0
- package/dist/types/codecs/request-codec.d.ts.map +1 -0
- package/dist/types/components/bleet-ajaxify.d.ts +17 -0
- package/dist/types/components/bleet-ajaxify.d.ts.map +1 -0
- package/dist/types/components/bleet-ajaxify.html.d.ts +3 -0
- package/dist/types/components/bleet-ajaxify.html.d.ts.map +1 -0
- package/dist/types/components/bleet-drawer.d.ts +40 -0
- package/dist/types/components/bleet-drawer.d.ts.map +1 -0
- package/dist/types/components/bleet-drawer.html.d.ts +3 -0
- package/dist/types/components/bleet-drawer.html.d.ts.map +1 -0
- package/dist/types/components/bleet-modal.d.ts +46 -0
- package/dist/types/components/bleet-modal.d.ts.map +1 -0
- package/dist/types/components/bleet-modal.html.d.ts +3 -0
- package/dist/types/components/bleet-modal.html.d.ts.map +1 -0
- package/dist/types/components/bleet-overlay.d.ts +21 -0
- package/dist/types/components/bleet-overlay.d.ts.map +1 -0
- package/dist/types/components/bleet-quilljs.d.ts +19 -0
- package/dist/types/components/bleet-quilljs.d.ts.map +1 -0
- package/dist/types/components/bleet-quilljs.html.d.ts +3 -0
- package/dist/types/components/bleet-quilljs.html.d.ts.map +1 -0
- package/dist/types/components/bleet-toast.d.ts +26 -0
- package/dist/types/components/bleet-toast.d.ts.map +1 -0
- package/dist/types/components/bleet-toast.html.d.ts +3 -0
- package/dist/types/components/bleet-toast.html.d.ts.map +1 -0
- package/dist/types/components/bleet-toaster-trigger.d.ts +20 -0
- package/dist/types/components/bleet-toaster-trigger.d.ts.map +1 -0
- package/dist/types/components/bleet-toaster.d.ts +15 -0
- package/dist/types/components/bleet-toaster.d.ts.map +1 -0
- package/dist/types/components/bleet-toaster.html.d.ts +3 -0
- package/dist/types/components/bleet-toaster.html.d.ts.map +1 -0
- package/dist/types/components/index.d.ts +9 -0
- package/dist/types/components/index.d.ts.map +1 -0
- package/dist/types/configure.d.ts +35 -0
- package/dist/types/configure.d.ts.map +1 -0
- package/dist/types/enums/api.d.ts +11 -0
- package/dist/types/enums/api.d.ts.map +1 -0
- package/dist/types/enums/event-aggregator.d.ts +123 -0
- package/dist/types/enums/event-aggregator.d.ts.map +1 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/interfaces/api.d.ts +56 -0
- package/dist/types/interfaces/api.d.ts.map +1 -0
- package/dist/types/interfaces/dialog.d.ts +18 -0
- package/dist/types/interfaces/dialog.d.ts.map +1 -0
- package/dist/types/interfaces/event-aggregator.d.ts +75 -0
- package/dist/types/interfaces/event-aggregator.d.ts.map +1 -0
- package/dist/types/services/api-service.d.ts +64 -0
- package/dist/types/services/api-service.d.ts.map +1 -0
- package/dist/types/services/http-service.d.ts +22 -0
- package/dist/types/services/http-service.d.ts.map +1 -0
- package/dist/types/services/socketio-service.d.ts +23 -0
- package/dist/types/services/socketio-service.d.ts.map +1 -0
- package/dist/types/services/storage-service.d.ts +13 -0
- package/dist/types/services/storage-service.d.ts.map +1 -0
- package/dist/types/services/svg-service.d.ts +17 -0
- package/dist/types/services/svg-service.d.ts.map +1 -0
- package/dist/types/services/transition-service.d.ts +13 -0
- package/dist/types/services/transition-service.d.ts.map +1 -0
- package/dist/types/services/trap-focus-service.d.ts +28 -0
- package/dist/types/services/trap-focus-service.d.ts.map +1 -0
- package/doc/bleet-api-reference.md +1333 -0
- package/doc/bleet-model-api-reference.md +379 -0
- package/doc/bleet-typescript-api-reference.md +1037 -0
- package/package.json +43 -0
- package/resource.d.ts +22 -0
- package/src/attributes/ajaxify-trigger.ts +218 -0
- package/src/attributes/alert.ts +55 -0
- package/src/attributes/badge.ts +39 -0
- package/src/attributes/burger.ts +36 -0
- package/src/attributes/drawer-trigger.ts +53 -0
- package/src/attributes/dropdown.ts +377 -0
- package/src/attributes/index.ts +15 -0
- package/src/attributes/menu.ts +179 -0
- package/src/attributes/modal-trigger.ts +53 -0
- package/src/attributes/pager.ts +43 -0
- package/src/attributes/password.ts +47 -0
- package/src/attributes/profile.ts +112 -0
- package/src/attributes/select.ts +214 -0
- package/src/attributes/tabs.ts +99 -0
- package/src/attributes/toaster-trigger.ts +54 -0
- package/src/attributes/upload.ts +380 -0
- package/src/codecs/ajaxify-codec.ts +16 -0
- package/src/codecs/csrf-codec.ts +41 -0
- package/src/codecs/request-codec.ts +16 -0
- package/src/components/bleet-ajaxify.html.ts +4 -0
- package/src/components/bleet-ajaxify.ts +62 -0
- package/src/components/bleet-drawer.html.ts +36 -0
- package/src/components/bleet-drawer.ts +236 -0
- package/src/components/bleet-modal.html.ts +30 -0
- package/src/components/bleet-modal.ts +274 -0
- package/src/components/bleet-overlay.ts +111 -0
- package/src/components/bleet-quilljs.html.ts +4 -0
- package/src/components/bleet-quilljs.ts +73 -0
- package/src/components/bleet-toast.html.ts +44 -0
- package/src/components/bleet-toast.ts +133 -0
- package/src/components/bleet-toaster-trigger.ts +66 -0
- package/src/components/bleet-toaster.html.ts +11 -0
- package/src/components/bleet-toaster.ts +72 -0
- package/src/components/index.ts +8 -0
- package/src/configure.ts +121 -0
- package/src/enums/api.ts +12 -0
- package/src/enums/event-aggregator.ts +131 -0
- package/src/index.ts +220 -0
- package/src/interfaces/api.ts +64 -0
- package/src/interfaces/dialog.ts +25 -0
- package/src/interfaces/event-aggregator.ts +88 -0
- package/src/services/api-service.ts +387 -0
- package/src/services/http-service.ts +166 -0
- package/src/services/socketio-service.ts +138 -0
- package/src/services/storage-service.ts +36 -0
- package/src/services/svg-service.ts +35 -0
- package/src/services/transition-service.ts +39 -0
- package/src/services/trap-focus-service.ts +213 -0
- package/src/types/css.d.ts +4 -0
- package/src/types/html.d.ts +12 -0
- package/src/types/svg.d.ts +4 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { bindable, customElement, IDisposable, IEventAggregator, ILogger, IPlatform, resolve } from 'aurelia';
|
|
2
|
+
import { Channels, DialogAction, DrawerAction, DrawerStatus, OverlayAction, ToasterAction, UiColor } from '../enums/event-aggregator';
|
|
3
|
+
import { IDrawer, IDrawerStatus, IOverlay, IToast, IToaster } from '../interfaces/event-aggregator';
|
|
4
|
+
import { IDialogResponse } from '../interfaces/dialog';
|
|
5
|
+
import { ITransitionService } from '../services/transition-service';
|
|
6
|
+
import { ISvgService } from '../services/svg-service';
|
|
7
|
+
import { IApiService } from '../services/api-service';
|
|
8
|
+
import template from './bleet-drawer.html';
|
|
9
|
+
|
|
10
|
+
@customElement({ name: 'bleet-drawer', template })
|
|
11
|
+
export class BleetDrawer {
|
|
12
|
+
@bindable id: string = '';
|
|
13
|
+
|
|
14
|
+
private dialogElement: HTMLDialogElement;
|
|
15
|
+
private disposable?: IDisposable;
|
|
16
|
+
|
|
17
|
+
// State
|
|
18
|
+
private loading: boolean = false;
|
|
19
|
+
private color: UiColor = UiColor.Primary;
|
|
20
|
+
private headerView: string | null = null;
|
|
21
|
+
private contentView: string | null = null;
|
|
22
|
+
private footerView: string | null = null;
|
|
23
|
+
|
|
24
|
+
// Color classes for header
|
|
25
|
+
private static readonly HEADER_BG_CLASSES: Record<UiColor, string> = {
|
|
26
|
+
[UiColor.Primary]: 'bg-primary-700',
|
|
27
|
+
[UiColor.Secondary]: 'bg-secondary-700',
|
|
28
|
+
[UiColor.Success]: 'bg-success-700',
|
|
29
|
+
[UiColor.Danger]: 'bg-danger-700',
|
|
30
|
+
[UiColor.Warning]: 'bg-warning-700',
|
|
31
|
+
[UiColor.Info]: 'bg-info-700',
|
|
32
|
+
[UiColor.Accent]: 'bg-accent-700',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private static readonly CLOSE_BUTTON_TEXT_CLASSES: Record<UiColor, string> = {
|
|
36
|
+
[UiColor.Primary]: 'text-primary-200',
|
|
37
|
+
[UiColor.Secondary]: 'text-secondary-200',
|
|
38
|
+
[UiColor.Success]: 'text-success-200',
|
|
39
|
+
[UiColor.Danger]: 'text-danger-200',
|
|
40
|
+
[UiColor.Warning]: 'text-warning-200',
|
|
41
|
+
[UiColor.Info]: 'text-info-200',
|
|
42
|
+
[UiColor.Accent]: 'text-accent-200',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
public constructor(
|
|
46
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('<bleet-drawer>'),
|
|
47
|
+
private readonly ea: IEventAggregator = resolve(IEventAggregator),
|
|
48
|
+
private readonly platform: IPlatform = resolve(IPlatform),
|
|
49
|
+
private readonly transitionService: ITransitionService = resolve(ITransitionService),
|
|
50
|
+
private readonly svgService: ISvgService = resolve(ISvgService),
|
|
51
|
+
private readonly apiService: IApiService = resolve(IApiService),
|
|
52
|
+
) {}
|
|
53
|
+
|
|
54
|
+
// Getters
|
|
55
|
+
private get headerBgClass(): string {
|
|
56
|
+
return BleetDrawer.HEADER_BG_CLASSES[this.color] ?? BleetDrawer.HEADER_BG_CLASSES[UiColor.Primary];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private get closeButtonTextClass(): string {
|
|
60
|
+
return BleetDrawer.CLOSE_BUTTON_TEXT_CLASSES[this.color] ?? BleetDrawer.CLOSE_BUTTON_TEXT_CLASSES[UiColor.Primary];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Lifecycle
|
|
64
|
+
public attached(): void {
|
|
65
|
+
this.disposable = this.ea.subscribe(Channels.Drawer, this.onDrawerEvent);
|
|
66
|
+
this.dialogElement.addEventListener('close', this.onCloseEvent);
|
|
67
|
+
this.dialogElement.addEventListener('cancel', this.onCancelEvent);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public detached(): void {
|
|
71
|
+
this.dialogElement.removeEventListener('close', this.onCloseEvent);
|
|
72
|
+
this.dialogElement.removeEventListener('cancel', this.onCancelEvent);
|
|
73
|
+
this.disposable?.dispose();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private bindDialogEvents(): void {
|
|
77
|
+
this.dialogElement.addEventListener('submit', this.onFormSubmit);
|
|
78
|
+
this.dialogElement.addEventListener('click', this.onDialogClick);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private unbindDialogEvents(): void {
|
|
82
|
+
this.dialogElement.removeEventListener('submit', this.onFormSubmit);
|
|
83
|
+
this.dialogElement.removeEventListener('click', this.onDialogClick);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handlers
|
|
87
|
+
private onDrawerEvent = (data: IDrawer): void => {
|
|
88
|
+
if (this.id && this.id !== '' && data.id === this.id) {
|
|
89
|
+
if (data.action === DrawerAction.Open && this.dialogElement.open !== true) {
|
|
90
|
+
if (data.url) {
|
|
91
|
+
this.loadFromUrl(data.url);
|
|
92
|
+
}
|
|
93
|
+
} else if (data.action === DrawerAction.Close && this.dialogElement.open === true) {
|
|
94
|
+
this.close();
|
|
95
|
+
} else if (data.action === DrawerAction.Toggle) {
|
|
96
|
+
if (this.dialogElement.open === true) {
|
|
97
|
+
this.close();
|
|
98
|
+
} else if (data.url) {
|
|
99
|
+
this.loadFromUrl(data.url);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private onCloseEvent = (event: Event): void => {
|
|
106
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{ action: OverlayAction.Close });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private onCancelEvent = (event: Event): void => {
|
|
110
|
+
this.close();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private onDialogClick = (event: MouseEvent): void => {
|
|
114
|
+
const target = event.target as HTMLElement;
|
|
115
|
+
if (target.closest('[data-drawer="close"]')) {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
this.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private onFormSubmit = (event: SubmitEvent): void => {
|
|
122
|
+
const form = (event.target as HTMLElement).closest('form');
|
|
123
|
+
if (!form) return;
|
|
124
|
+
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
|
|
127
|
+
const formData = new FormData(form);
|
|
128
|
+
|
|
129
|
+
// Capture submitter button name/value (not included by default in FormData)
|
|
130
|
+
if (event.submitter instanceof HTMLButtonElement && event.submitter.name) {
|
|
131
|
+
formData.append(event.submitter.name, event.submitter.value || '');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const method = (formData.get('_method') as string) || form.getAttribute('method') || 'POST';
|
|
135
|
+
|
|
136
|
+
this.apiService
|
|
137
|
+
.url(form.action)
|
|
138
|
+
.fromMultipart(formData)
|
|
139
|
+
.request<IDialogResponse>(method)
|
|
140
|
+
.then((response) => {
|
|
141
|
+
this.applyResponse(response.body);
|
|
142
|
+
})
|
|
143
|
+
.catch((error) => {
|
|
144
|
+
this.logger.error('form submit failed', error);
|
|
145
|
+
this.ea.publish(Channels.Toaster, <IToaster>{
|
|
146
|
+
action: ToasterAction.Add,
|
|
147
|
+
toast: { color: UiColor.Danger, content: 'Erreur lors de l\'envoi' } as IToast
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private loadFromUrl(url: string): void {
|
|
153
|
+
this.loading = true;
|
|
154
|
+
this.open();
|
|
155
|
+
|
|
156
|
+
this.apiService.url(url).get<IDialogResponse>()
|
|
157
|
+
.then((response) => {
|
|
158
|
+
this.applyResponse(response.body);
|
|
159
|
+
})
|
|
160
|
+
.catch((error) => {
|
|
161
|
+
this.logger.error('loadFromUrl failed', error);
|
|
162
|
+
this.close();
|
|
163
|
+
this.ea.publish(Channels.Toaster, <IToaster>{
|
|
164
|
+
action: ToasterAction.Add,
|
|
165
|
+
toast: { color: UiColor.Danger, content: 'Erreur de chargement' } as IToast
|
|
166
|
+
});
|
|
167
|
+
})
|
|
168
|
+
.finally(() => {
|
|
169
|
+
this.loading = false;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private applyResponse(response: IDialogResponse): void {
|
|
174
|
+
this.color = response.color ?? UiColor.Primary;
|
|
175
|
+
if (response.header) this.headerView = response.header;
|
|
176
|
+
if (response.content) this.contentView = response.content;
|
|
177
|
+
if (response.footer) this.footerView = response.footer;
|
|
178
|
+
this.executeActions(response);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private executeActions(response: IDialogResponse): void {
|
|
182
|
+
// 1. Primary action
|
|
183
|
+
if (response.action === DialogAction.Close) {
|
|
184
|
+
this.close();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 2. Secondary actions (combinable)
|
|
188
|
+
if (response.toast) {
|
|
189
|
+
this.ea.publish(Channels.Toaster, <IToaster>{
|
|
190
|
+
action: ToasterAction.Add,
|
|
191
|
+
toast: response.toast
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (response.ajaxify) {
|
|
195
|
+
this.ea.publish(Channels.Ajaxify, response.ajaxify);
|
|
196
|
+
} else if (response.redirect) {
|
|
197
|
+
this.platform.window.location.href = response.redirect;
|
|
198
|
+
} else if (response.refresh) {
|
|
199
|
+
this.platform.window.location.reload();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Open / Close — translate-x
|
|
204
|
+
private open(): void {
|
|
205
|
+
this.bindDialogEvents();
|
|
206
|
+
this.transitionService.run(this.dialogElement, (element) => {
|
|
207
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{ action: OverlayAction.Open });
|
|
208
|
+
this.ea.publish(Channels.DrawerStatus, <IDrawerStatus>{ status: DrawerStatus.Opening, id: this.id });
|
|
209
|
+
(element as HTMLDialogElement).showModal();
|
|
210
|
+
this.platform.requestAnimationFrame(() => {
|
|
211
|
+
element.classList.add('translate-x-0');
|
|
212
|
+
element.classList.remove('translate-x-full');
|
|
213
|
+
});
|
|
214
|
+
}, () => {
|
|
215
|
+
this.ea.publish(Channels.DrawerStatus, <IDrawerStatus>{ status: DrawerStatus.Opened, id: this.id });
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private close(): void {
|
|
220
|
+
this.transitionService.run(this.dialogElement, (element) => {
|
|
221
|
+
this.ea.publish(Channels.DrawerStatus, <IDrawerStatus>{ status: DrawerStatus.Closing, id: this.id });
|
|
222
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{ action: OverlayAction.Close });
|
|
223
|
+
element.classList.add('translate-x-full');
|
|
224
|
+
element.classList.remove('translate-x-0');
|
|
225
|
+
}, (element) => {
|
|
226
|
+
(element as HTMLDialogElement).close();
|
|
227
|
+
this.unbindDialogEvents();
|
|
228
|
+
this.headerView = null;
|
|
229
|
+
this.contentView = null;
|
|
230
|
+
this.footerView = null;
|
|
231
|
+
this.color = UiColor.Primary;
|
|
232
|
+
this.loading = false;
|
|
233
|
+
this.ea.publish(Channels.DrawerStatus, <IDrawerStatus>{ status: DrawerStatus.Closed, id: this.id });
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export default `<template>
|
|
2
|
+
<dialog ref="dialogElement"
|
|
3
|
+
class="fixed inset-0 z-50 size-auto max-h-none max-w-none overflow-y-auto transition ease-in-out duration-300 bg-transparent backdrop:bg-transparent opacity-0">
|
|
4
|
+
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
|
5
|
+
<div class="relative transform overflow-hidden rounded-lg bg-white text-left shadow-xl sm:my-8 sm:w-full sm:max-w-lg">
|
|
6
|
+
|
|
7
|
+
<!-- Loader -->
|
|
8
|
+
<div if.bind="loading" class="flex items-center justify-center py-12">
|
|
9
|
+
<svg class="animate-spin size-8 text-primary-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
10
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
11
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
12
|
+
</svg>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Contenu -->
|
|
16
|
+
<template else>
|
|
17
|
+
<!-- Header -->
|
|
18
|
+
<au-compose if.bind="headerView" template.bind="headerView"></au-compose>
|
|
19
|
+
|
|
20
|
+
<!-- Content -->
|
|
21
|
+
<au-compose if.bind="contentView" template.bind="contentView"></au-compose>
|
|
22
|
+
|
|
23
|
+
<!-- Footer -->
|
|
24
|
+
<au-compose if.bind="footerView" template.bind="footerView"></au-compose>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</dialog>
|
|
30
|
+
</template>`
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { bindable, customElement, IDisposable, IEventAggregator, ILogger, IPlatform, resolve } from 'aurelia';
|
|
2
|
+
import { Channels, DialogAction, ModalAction, ModalStatus, OverlayAction, ToasterAction, UiColor } from '../enums/event-aggregator';
|
|
3
|
+
import { IModal, IModalStatus, IOverlay, IToast, IToaster } from '../interfaces/event-aggregator';
|
|
4
|
+
import { IDialogResponse } from '../interfaces/dialog';
|
|
5
|
+
import { ITransitionService } from '../services/transition-service';
|
|
6
|
+
import { ISvgService } from '../services/svg-service';
|
|
7
|
+
import { IApiService } from '../services/api-service';
|
|
8
|
+
import template from './bleet-modal.html';
|
|
9
|
+
|
|
10
|
+
@customElement({ name: 'bleet-modal', template })
|
|
11
|
+
export class BleetModal {
|
|
12
|
+
@bindable id: string = '';
|
|
13
|
+
|
|
14
|
+
private dialogElement: HTMLDialogElement;
|
|
15
|
+
private disposable?: IDisposable;
|
|
16
|
+
|
|
17
|
+
// State
|
|
18
|
+
private loading: boolean = false;
|
|
19
|
+
private color: UiColor = UiColor.Primary;
|
|
20
|
+
private icon: string | null = null;
|
|
21
|
+
private headerView: string | null = null;
|
|
22
|
+
private contentView: string | null = null;
|
|
23
|
+
private footerView: string | null = null;
|
|
24
|
+
|
|
25
|
+
// Color classes — no Tailwind interpolation
|
|
26
|
+
private static readonly ICON_BG_CLASSES: Record<UiColor, string> = {
|
|
27
|
+
[UiColor.Primary]: 'bg-primary-100',
|
|
28
|
+
[UiColor.Secondary]: 'bg-secondary-100',
|
|
29
|
+
[UiColor.Success]: 'bg-success-100',
|
|
30
|
+
[UiColor.Danger]: 'bg-danger-100',
|
|
31
|
+
[UiColor.Warning]: 'bg-warning-100',
|
|
32
|
+
[UiColor.Info]: 'bg-info-100',
|
|
33
|
+
[UiColor.Accent]: 'bg-accent-100',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
private static readonly ICON_TEXT_CLASSES: Record<UiColor, string> = {
|
|
37
|
+
[UiColor.Primary]: 'text-primary-600',
|
|
38
|
+
[UiColor.Secondary]: 'text-secondary-600',
|
|
39
|
+
[UiColor.Success]: 'text-success-600',
|
|
40
|
+
[UiColor.Danger]: 'text-danger-600',
|
|
41
|
+
[UiColor.Warning]: 'text-warning-600',
|
|
42
|
+
[UiColor.Info]: 'text-info-600',
|
|
43
|
+
[UiColor.Accent]: 'text-accent-600',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
private static readonly HEADER_BG_CLASSES: Record<UiColor, string> = {
|
|
47
|
+
[UiColor.Primary]: 'bg-primary-600',
|
|
48
|
+
[UiColor.Secondary]: 'bg-secondary-600',
|
|
49
|
+
[UiColor.Success]: 'bg-success-600',
|
|
50
|
+
[UiColor.Danger]: 'bg-danger-600',
|
|
51
|
+
[UiColor.Warning]: 'bg-warning-600',
|
|
52
|
+
[UiColor.Info]: 'bg-info-600',
|
|
53
|
+
[UiColor.Accent]: 'bg-accent-600',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
private static readonly CLOSE_BUTTON_TEXT_CLASSES: Record<UiColor, string> = {
|
|
57
|
+
[UiColor.Primary]: 'text-primary-200',
|
|
58
|
+
[UiColor.Secondary]: 'text-secondary-200',
|
|
59
|
+
[UiColor.Success]: 'text-success-200',
|
|
60
|
+
[UiColor.Danger]: 'text-danger-200',
|
|
61
|
+
[UiColor.Warning]: 'text-warning-200',
|
|
62
|
+
[UiColor.Info]: 'text-info-200',
|
|
63
|
+
[UiColor.Accent]: 'text-accent-200',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
public constructor(
|
|
67
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('<bleet-modal>'),
|
|
68
|
+
private readonly ea: IEventAggregator = resolve(IEventAggregator),
|
|
69
|
+
private readonly platform: IPlatform = resolve(IPlatform),
|
|
70
|
+
private readonly transitionService: ITransitionService = resolve(ITransitionService),
|
|
71
|
+
private readonly svgService: ISvgService = resolve(ISvgService),
|
|
72
|
+
private readonly apiService: IApiService = resolve(IApiService),
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
// Getters
|
|
76
|
+
private get iconSvg(): string | null {
|
|
77
|
+
if (!this.icon) return null;
|
|
78
|
+
return this.svgService.get(this.icon);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private get iconBgClass(): string {
|
|
82
|
+
return BleetModal.ICON_BG_CLASSES[this.color] ?? BleetModal.ICON_BG_CLASSES[UiColor.Primary];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private get iconTextClass(): string {
|
|
86
|
+
return BleetModal.ICON_TEXT_CLASSES[this.color] ?? BleetModal.ICON_TEXT_CLASSES[UiColor.Primary];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private get headerBgClass(): string {
|
|
90
|
+
return BleetModal.HEADER_BG_CLASSES[this.color] ?? BleetModal.HEADER_BG_CLASSES[UiColor.Primary];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private get closeButtonTextClass(): string {
|
|
94
|
+
return BleetModal.CLOSE_BUTTON_TEXT_CLASSES[this.color] ?? BleetModal.CLOSE_BUTTON_TEXT_CLASSES[UiColor.Primary];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Lifecycle
|
|
98
|
+
public attached(): void {
|
|
99
|
+
this.disposable = this.ea.subscribe(Channels.Modal, this.onModalEvent);
|
|
100
|
+
this.dialogElement.addEventListener('close', this.onCloseEvent);
|
|
101
|
+
this.dialogElement.addEventListener('cancel', this.onCancelEvent);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public detached(): void {
|
|
105
|
+
this.dialogElement.removeEventListener('close', this.onCloseEvent);
|
|
106
|
+
this.dialogElement.removeEventListener('cancel', this.onCancelEvent);
|
|
107
|
+
this.disposable?.dispose();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private bindDialogEvents(): void {
|
|
111
|
+
this.dialogElement.addEventListener('submit', this.onFormSubmit);
|
|
112
|
+
this.dialogElement.addEventListener('click', this.onDialogClick);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private unbindDialogEvents(): void {
|
|
116
|
+
this.dialogElement.removeEventListener('submit', this.onFormSubmit);
|
|
117
|
+
this.dialogElement.removeEventListener('click', this.onDialogClick);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Handlers
|
|
121
|
+
private onModalEvent = (data: IModal): void => {
|
|
122
|
+
if (this.id && this.id !== '' && data.id === this.id) {
|
|
123
|
+
if (data.action === ModalAction.Open && this.dialogElement.open !== true) {
|
|
124
|
+
if (data.url) {
|
|
125
|
+
this.loadFromUrl(data.url);
|
|
126
|
+
}
|
|
127
|
+
} else if (data.action === ModalAction.Close && this.dialogElement.open === true) {
|
|
128
|
+
this.close();
|
|
129
|
+
} else if (data.action === ModalAction.Toggle) {
|
|
130
|
+
if (this.dialogElement.open === true) {
|
|
131
|
+
this.close();
|
|
132
|
+
} else if (data.url) {
|
|
133
|
+
this.loadFromUrl(data.url);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private onCloseEvent = (event: Event): void => {
|
|
140
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{ action: OverlayAction.Close });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private onCancelEvent = (event: Event): void => {
|
|
144
|
+
this.close();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private onDialogClick = (event: MouseEvent): void => {
|
|
148
|
+
const target = event.target as HTMLElement;
|
|
149
|
+
if (target.closest('[data-modal="close"]')) {
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
this.close();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private onFormSubmit = (event: SubmitEvent): void => {
|
|
156
|
+
const form = (event.target as HTMLElement).closest('form');
|
|
157
|
+
if (!form) return;
|
|
158
|
+
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
|
|
161
|
+
const formData = new FormData(form);
|
|
162
|
+
const method = (formData.get('_method') as string) || form.getAttribute('method') || 'POST';
|
|
163
|
+
|
|
164
|
+
this.apiService
|
|
165
|
+
.url(form.action)
|
|
166
|
+
.fromMultipart(formData)
|
|
167
|
+
.request<IDialogResponse>(method)
|
|
168
|
+
.then((response) => {
|
|
169
|
+
this.applyResponse(response.body);
|
|
170
|
+
})
|
|
171
|
+
.catch((error) => {
|
|
172
|
+
this.logger.error('form submit failed', error);
|
|
173
|
+
this.ea.publish(Channels.Toaster, <IToaster>{
|
|
174
|
+
action: ToasterAction.Add,
|
|
175
|
+
toast: { color: UiColor.Danger, content: 'Erreur lors de l\'envoi' } as IToast
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// AJAX
|
|
181
|
+
private loadFromUrl(url: string): void {
|
|
182
|
+
this.loading = true;
|
|
183
|
+
this.open();
|
|
184
|
+
|
|
185
|
+
this.apiService.url(url).get<IDialogResponse>()
|
|
186
|
+
.then((response) => {
|
|
187
|
+
this.applyResponse(response.body);
|
|
188
|
+
})
|
|
189
|
+
.catch((error) => {
|
|
190
|
+
this.logger.error('loadFromUrl failed', error);
|
|
191
|
+
this.close();
|
|
192
|
+
this.ea.publish(Channels.Toaster, <IToaster>{
|
|
193
|
+
action: ToasterAction.Add,
|
|
194
|
+
toast: { color: UiColor.Danger, content: 'Erreur de chargement' } as IToast
|
|
195
|
+
});
|
|
196
|
+
})
|
|
197
|
+
.finally(() => {
|
|
198
|
+
this.loading = false;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private applyResponse(response: IDialogResponse): void {
|
|
203
|
+
// Style
|
|
204
|
+
this.color = response.color ?? UiColor.Primary;
|
|
205
|
+
this.icon = response.icon ?? null;
|
|
206
|
+
|
|
207
|
+
// Content
|
|
208
|
+
if (response.header) this.headerView = response.header;
|
|
209
|
+
if (response.content) this.contentView = response.content;
|
|
210
|
+
if (response.footer) this.footerView = response.footer;
|
|
211
|
+
|
|
212
|
+
// Actions
|
|
213
|
+
this.executeActions(response);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private executeActions(response: IDialogResponse): void {
|
|
217
|
+
// 1. Primary action
|
|
218
|
+
if (response.action === DialogAction.Close) {
|
|
219
|
+
this.close();
|
|
220
|
+
}
|
|
221
|
+
// DialogAction.Keep → do nothing, dialog stays open
|
|
222
|
+
|
|
223
|
+
// 2. Secondary actions (combinable)
|
|
224
|
+
if (response.toast) {
|
|
225
|
+
this.ea.publish(Channels.Toaster, <IToaster>{
|
|
226
|
+
action: ToasterAction.Add,
|
|
227
|
+
toast: response.toast
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (response.ajaxify) {
|
|
231
|
+
this.ea.publish(Channels.Ajaxify, response.ajaxify);
|
|
232
|
+
} else if (response.redirect) {
|
|
233
|
+
this.platform.window.location.href = response.redirect;
|
|
234
|
+
} else if (response.refresh) {
|
|
235
|
+
this.platform.window.location.reload();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Open / Close
|
|
240
|
+
private open(): void {
|
|
241
|
+
this.bindDialogEvents();
|
|
242
|
+
this.transitionService.run(this.dialogElement, (element) => {
|
|
243
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{ action: OverlayAction.Open });
|
|
244
|
+
this.ea.publish(Channels.ModalStatus, <IModalStatus>{ status: ModalStatus.Opening, id: this.id });
|
|
245
|
+
(element as HTMLDialogElement).showModal();
|
|
246
|
+
this.platform.requestAnimationFrame(() => {
|
|
247
|
+
element.classList.add('opacity-100');
|
|
248
|
+
element.classList.remove('opacity-0');
|
|
249
|
+
});
|
|
250
|
+
}, () => {
|
|
251
|
+
this.ea.publish(Channels.ModalStatus, <IModalStatus>{ status: ModalStatus.Opened, id: this.id });
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private close(): void {
|
|
256
|
+
this.transitionService.run(this.dialogElement, (element) => {
|
|
257
|
+
this.ea.publish(Channels.ModalStatus, <IModalStatus>{ status: ModalStatus.Closing, id: this.id });
|
|
258
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{ action: OverlayAction.Close });
|
|
259
|
+
element.classList.add('opacity-0');
|
|
260
|
+
element.classList.remove('opacity-100');
|
|
261
|
+
}, (element) => {
|
|
262
|
+
(element as HTMLDialogElement).close();
|
|
263
|
+
this.unbindDialogEvents();
|
|
264
|
+
// Reset
|
|
265
|
+
this.headerView = null;
|
|
266
|
+
this.contentView = null;
|
|
267
|
+
this.footerView = null;
|
|
268
|
+
this.icon = null;
|
|
269
|
+
this.color = UiColor.Primary;
|
|
270
|
+
this.loading = false;
|
|
271
|
+
this.ea.publish(Channels.ModalStatus, <IModalStatus>{ status: ModalStatus.Closed, id: this.id });
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {customElement, IDisposable, IEventAggregator, ILogger, INode, IPlatform, resolve} from "aurelia";
|
|
2
|
+
import {Channels, OverlayAction, OverlayStatus} from '../enums/event-aggregator';
|
|
3
|
+
import {IOverlay, IOverlayStatus} from '../interfaces/event-aggregator';
|
|
4
|
+
import {ITransitionService} from '../services/transition-service';
|
|
5
|
+
|
|
6
|
+
@customElement({
|
|
7
|
+
name: 'bleet-overlay',
|
|
8
|
+
template: null,
|
|
9
|
+
})
|
|
10
|
+
export class BleetOverlay {
|
|
11
|
+
|
|
12
|
+
private disposable?: IDisposable;
|
|
13
|
+
public constructor(
|
|
14
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('<bleet-overlay>'),
|
|
15
|
+
private readonly element: HTMLElement = resolve(INode) as HTMLElement,
|
|
16
|
+
private readonly ea: IEventAggregator = resolve(IEventAggregator),
|
|
17
|
+
private readonly platform: IPlatform = resolve(IPlatform),
|
|
18
|
+
private readonly transitionService: ITransitionService = resolve(ITransitionService),
|
|
19
|
+
) {
|
|
20
|
+
this.logger.trace('constructor')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public attaching()
|
|
24
|
+
{
|
|
25
|
+
this.logger.trace('attaching');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public attached()
|
|
29
|
+
{
|
|
30
|
+
this.logger.trace('attached');
|
|
31
|
+
this.disposable = this.ea.subscribe(Channels.Overlay, this.onOverlayEvent);
|
|
32
|
+
this.element.addEventListener('click', this.onClickOverlay);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public detached()
|
|
36
|
+
{
|
|
37
|
+
this.logger.trace('detached');
|
|
38
|
+
this.element.removeEventListener('click', this.onClickOverlay);
|
|
39
|
+
this.disposable?.dispose();
|
|
40
|
+
}
|
|
41
|
+
public dispose()
|
|
42
|
+
{
|
|
43
|
+
this.logger.trace('dispose');
|
|
44
|
+
this.disposable?.dispose();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private toggle(fromOverlay: boolean = false) {
|
|
48
|
+
if (this.element.classList.contains('hidden')) {
|
|
49
|
+
this.open(fromOverlay);
|
|
50
|
+
} else {
|
|
51
|
+
this.close(fromOverlay);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
private open(fromOverlay: boolean = false) {
|
|
55
|
+
if (this.element.classList.contains('hidden')) {
|
|
56
|
+
this.logger.trace('open');
|
|
57
|
+
this.transitionService.run(this.element, (element: HTMLElement) => {
|
|
58
|
+
if (fromOverlay) {
|
|
59
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{action: OverlayAction.Open});
|
|
60
|
+
}
|
|
61
|
+
this.ea.publish(Channels.OverlayStatus, <IOverlayStatus>{status: OverlayStatus.Opening});
|
|
62
|
+
element.classList.remove('hidden');
|
|
63
|
+
this.platform.requestAnimationFrame(() => {
|
|
64
|
+
element.classList.remove('opacity-0');
|
|
65
|
+
element.classList.add('opacity-100');
|
|
66
|
+
});
|
|
67
|
+
this.logger.trace('open before()');
|
|
68
|
+
}, (element: HTMLElement) => {
|
|
69
|
+
this.ea.publish(Channels.OverlayStatus, <IOverlayStatus>{status: OverlayStatus.Opened});
|
|
70
|
+
this.logger.trace('open after()');
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
private close(fromOverlay: boolean = false) {
|
|
75
|
+
if (!this.element.classList.contains('hidden')) {
|
|
76
|
+
this.transitionService.run(this.element, (element: HTMLElement) => {
|
|
77
|
+
if (fromOverlay) {
|
|
78
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{action: OverlayAction.Close});
|
|
79
|
+
}
|
|
80
|
+
this.ea.publish(Channels.OverlayStatus, <IOverlayStatus>{status: OverlayStatus.Closing});
|
|
81
|
+
element.classList.remove('opacity-100');
|
|
82
|
+
element.classList.add('opacity-0');
|
|
83
|
+
this.logger.trace('close before()');
|
|
84
|
+
}, (element: HTMLElement) => {
|
|
85
|
+
element.classList.add('hidden');
|
|
86
|
+
this.logger.trace('close after()');
|
|
87
|
+
this.ea.publish(Channels.OverlayStatus, <IOverlayStatus>{status: OverlayStatus.Closed});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
private onOverlayEvent = (data: IOverlay) => {
|
|
92
|
+
if (data.action === OverlayAction.Open) {
|
|
93
|
+
this.logger.trace('onOverlayEvent', data);
|
|
94
|
+
this.open();
|
|
95
|
+
} else if (data.action === OverlayAction.Close) {
|
|
96
|
+
this.logger.trace('onOverlayEvent', data);
|
|
97
|
+
this.close();
|
|
98
|
+
} else if (data.action === OverlayAction.Toggle) {
|
|
99
|
+
this.logger.trace('onOverlayEvent', data);
|
|
100
|
+
this.toggle();
|
|
101
|
+
} else {
|
|
102
|
+
this.logger.trace('onOverlayEvent unhandled', data);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
private onClickOverlay = (event: MouseEvent) => {
|
|
106
|
+
this.logger.trace('onClickOverlay', event);
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
this.close(true);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|