@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,377 @@
|
|
|
1
|
+
import {customAttribute, ILogger, INode, resolve} from "aurelia";
|
|
2
|
+
import {ITrapFocusService} from '../services/trap-focus-service';
|
|
3
|
+
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
@customAttribute('bleet-dropdown')
|
|
6
|
+
export class BleetDropdownCustomAttribute {
|
|
7
|
+
|
|
8
|
+
private select?: HTMLSelectElement;
|
|
9
|
+
private button?: HTMLButtonElement;
|
|
10
|
+
private buttonText?: HTMLElement;
|
|
11
|
+
private optionTemplate?: HTMLTemplateElement;
|
|
12
|
+
private tagTemplate?: HTMLTemplateElement;
|
|
13
|
+
private tagsContainer?: HTMLElement;
|
|
14
|
+
private placeholder?: HTMLElement;
|
|
15
|
+
private itemsPlace?: HTMLElement;
|
|
16
|
+
private itemsContainer?: HTMLElement;
|
|
17
|
+
private searchInput?: HTMLInputElement;
|
|
18
|
+
private emptyMessage?: HTMLElement;
|
|
19
|
+
private isMultiple: boolean = false;
|
|
20
|
+
private withTags: boolean = false;
|
|
21
|
+
|
|
22
|
+
public constructor(
|
|
23
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-dropdown'),
|
|
24
|
+
private readonly element: HTMLElement = resolve(INode) as HTMLElement,
|
|
25
|
+
private readonly trapFocusService: ITrapFocusService = resolve(ITrapFocusService),
|
|
26
|
+
) {
|
|
27
|
+
this.logger.trace('constructor')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public attaching()
|
|
31
|
+
{
|
|
32
|
+
this.logger.trace('attaching');
|
|
33
|
+
this.select = this.element.querySelector('select') as HTMLSelectElement;
|
|
34
|
+
this.button = this.element.querySelector('button') as HTMLButtonElement;
|
|
35
|
+
this.buttonText = this.button.querySelector('[data-dropdown=value]') as HTMLElement;
|
|
36
|
+
this.tagsContainer = this.button.querySelector('[data-dropdown=tags]') as HTMLElement;
|
|
37
|
+
this.placeholder = this.button.querySelector('[data-dropdown=placeholder]') as HTMLElement;
|
|
38
|
+
this.optionTemplate = this.element.querySelector('[data-dropdown=item-template]') as HTMLTemplateElement;
|
|
39
|
+
this.tagTemplate = this.element.querySelector('[data-dropdown=tag-template]') as HTMLTemplateElement;
|
|
40
|
+
this.itemsPlace = this.element.querySelector('[data-dropdown=items]') as HTMLElement;
|
|
41
|
+
this.itemsContainer = this.element.querySelector('[data-dropdown=items-container]') as HTMLElement;
|
|
42
|
+
this.searchInput = this.element.querySelector('[data-dropdown=search]') as HTMLInputElement;
|
|
43
|
+
this.emptyMessage = this.element.querySelector('[data-dropdown=empty]') as HTMLElement;
|
|
44
|
+
this.isMultiple = this.select?.multiple || false;
|
|
45
|
+
this.withTags = this.tagTemplate !== null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public attached()
|
|
49
|
+
{
|
|
50
|
+
this.logger.trace('attached');
|
|
51
|
+
if (!this.itemsPlace) {
|
|
52
|
+
throw new Error('Items place element not found');
|
|
53
|
+
}
|
|
54
|
+
if (!this.itemsPlace.id) {
|
|
55
|
+
this.itemsPlace.id = `data-dropdown-items-${Math.random().toString(36).substring(2, 15)}`;
|
|
56
|
+
}
|
|
57
|
+
if (!this.select?.options) {
|
|
58
|
+
throw new Error('Select options not found');
|
|
59
|
+
}
|
|
60
|
+
if (!this.optionTemplate) {
|
|
61
|
+
throw new Error('Option template not found');
|
|
62
|
+
}
|
|
63
|
+
if (!this.button) {
|
|
64
|
+
throw new Error('Button element not found');
|
|
65
|
+
}
|
|
66
|
+
if (!this.buttonText && !this.tagsContainer) {
|
|
67
|
+
throw new Error('Button text or tags container element not found');
|
|
68
|
+
}
|
|
69
|
+
this.preparePanel();
|
|
70
|
+
|
|
71
|
+
// ARIA setup
|
|
72
|
+
this.button.ariaHasPopup = 'listbox';
|
|
73
|
+
this.button.setAttribute('aria-controls', this.itemsPlace.id);
|
|
74
|
+
this.itemsPlace.role = 'listbox';
|
|
75
|
+
|
|
76
|
+
// Event listeners
|
|
77
|
+
this.button?.addEventListener('click', this.onClickToggleMenu);
|
|
78
|
+
this.itemsPlace?.addEventListener('click', this.onClickToggleItem);
|
|
79
|
+
this.searchInput?.addEventListener('input', this.onSearchInput);
|
|
80
|
+
|
|
81
|
+
// Tag remove event listener (delegation)
|
|
82
|
+
if (this.withTags) {
|
|
83
|
+
this.tagsContainer?.addEventListener('click', this.onClickRemoveTag);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public detached()
|
|
88
|
+
{
|
|
89
|
+
this.logger.trace('detached');
|
|
90
|
+
this.button?.removeEventListener('click', this.onClickToggleMenu);
|
|
91
|
+
this.itemsPlace?.removeEventListener('click', this.onClickToggleItem);
|
|
92
|
+
this.searchInput?.removeEventListener('input', this.onSearchInput);
|
|
93
|
+
if (this.withTags) {
|
|
94
|
+
this.tagsContainer?.removeEventListener('click', this.onClickRemoveTag);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private onClickToggleMenu = (event: MouseEvent) => {
|
|
99
|
+
this.logger.trace('onClick', event);
|
|
100
|
+
// Ne pas ouvrir si on clique sur un bouton de suppression de tag
|
|
101
|
+
if ((event.target as HTMLElement).closest('[data-dropdown=tag-remove]')) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
return this.toggleMenu();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private onClickRemoveTag = (event: MouseEvent) => {
|
|
109
|
+
const removeButton = (event.target as HTMLElement).closest('[data-dropdown=tag-remove]');
|
|
110
|
+
if (!removeButton) return;
|
|
111
|
+
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
event.stopPropagation();
|
|
114
|
+
|
|
115
|
+
const tagElement = removeButton.closest('[data-tag-value]') as HTMLElement;
|
|
116
|
+
if (!tagElement) return;
|
|
117
|
+
|
|
118
|
+
const value = tagElement.dataset.tagValue;
|
|
119
|
+
|
|
120
|
+
// Désélectionner l'option
|
|
121
|
+
Array.from((this.select as HTMLSelectElement).options).forEach((option) => {
|
|
122
|
+
if (option.value == value) {
|
|
123
|
+
option.selected = false;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Dispatch change event sur le select natif pour active-form
|
|
128
|
+
this.select?.dispatchEvent(new Event('change', { bubbles: true }));
|
|
129
|
+
|
|
130
|
+
this.swapItemClasses();
|
|
131
|
+
this.updateDisplay();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private onStopTrapFocus = () => {
|
|
135
|
+
this.logger.trace('onStopTrapFocus');
|
|
136
|
+
this.itemsPlace?.classList.add('hidden');
|
|
137
|
+
// Reset search on close
|
|
138
|
+
if (this.searchInput) {
|
|
139
|
+
this.searchInput.value = '';
|
|
140
|
+
this.filterItems('');
|
|
141
|
+
}
|
|
142
|
+
return Promise.resolve();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private toggleMenu = () => {
|
|
146
|
+
const isClosed = this.itemsPlace?.classList.contains('hidden');
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
if (isClosed) {
|
|
149
|
+
// Opening menu
|
|
150
|
+
(this.button as HTMLButtonElement).ariaExpanded = 'true';
|
|
151
|
+
|
|
152
|
+
// Find selected option to focus initially, or search input if searchable
|
|
153
|
+
const initialFocus = this.searchInput || this.itemsPlace?.querySelector('[aria-selected="true"]') as HTMLElement;
|
|
154
|
+
|
|
155
|
+
return this.trapFocusService.start(
|
|
156
|
+
this.button as HTMLButtonElement,
|
|
157
|
+
this.itemsPlace as HTMLElement,
|
|
158
|
+
this.element,
|
|
159
|
+
undefined,
|
|
160
|
+
this.onStopTrapFocus,
|
|
161
|
+
initialFocus
|
|
162
|
+
)
|
|
163
|
+
.then(() => {
|
|
164
|
+
this.logger.trace('toggleMenu opened');
|
|
165
|
+
this.itemsPlace?.classList.remove('hidden');
|
|
166
|
+
resolve(void 0);
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
// Closing menu
|
|
170
|
+
(this.button as HTMLButtonElement).ariaExpanded = 'false';
|
|
171
|
+
return this.trapFocusService.stop()
|
|
172
|
+
.then(() => {
|
|
173
|
+
this.logger.trace('toggleMenu closed');
|
|
174
|
+
this.itemsPlace?.classList.add('hidden');
|
|
175
|
+
resolve(void 0);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private onClickToggleItem = (event: MouseEvent) => {
|
|
182
|
+
this.logger.trace('onClickItem', event);
|
|
183
|
+
event.preventDefault();
|
|
184
|
+
const element = (event.target as HTMLElement).closest('[data-value]') as HTMLElement;
|
|
185
|
+
if (!element) return;
|
|
186
|
+
|
|
187
|
+
const clickedValue = element.dataset.value;
|
|
188
|
+
|
|
189
|
+
if (this.isMultiple) {
|
|
190
|
+
// Toggle la sélection de l'option cliquée
|
|
191
|
+
Array.from((this.select as HTMLSelectElement).options).forEach((option) => {
|
|
192
|
+
if (option.value == clickedValue) {
|
|
193
|
+
option.selected = !option.selected;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Dispatch change event sur le select natif pour active-form
|
|
198
|
+
this.select?.dispatchEvent(new Event('change', { bubbles: true }));
|
|
199
|
+
|
|
200
|
+
// Ne pas fermer le menu en mode multiple
|
|
201
|
+
this.swapItemClasses();
|
|
202
|
+
this.updateDisplay();
|
|
203
|
+
} else {
|
|
204
|
+
// Mode simple : une seule sélection
|
|
205
|
+
Array.from((this.select as HTMLSelectElement).options).forEach((option) => {
|
|
206
|
+
option.selected = option.value == clickedValue;
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Dispatch change event sur le select natif pour active-form
|
|
210
|
+
this.select?.dispatchEvent(new Event('change', { bubbles: true }));
|
|
211
|
+
|
|
212
|
+
this.synchSelect();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private onSearchInput = (event: Event) => {
|
|
217
|
+
const query = (event.target as HTMLInputElement).value;
|
|
218
|
+
this.filterItems(query);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private filterItems(query: string) {
|
|
222
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
223
|
+
let visibleCount = 0;
|
|
224
|
+
|
|
225
|
+
this.itemsContainer?.querySelectorAll('[data-value]').forEach((item) => {
|
|
226
|
+
const text = item.querySelector('[data-dropdown=item-text]')?.textContent?.toLowerCase() || '';
|
|
227
|
+
const isVisible = normalizedQuery === '' || text.includes(normalizedQuery);
|
|
228
|
+
|
|
229
|
+
if (isVisible) {
|
|
230
|
+
(item as HTMLElement).classList.remove('hidden');
|
|
231
|
+
visibleCount++;
|
|
232
|
+
} else {
|
|
233
|
+
(item as HTMLElement).classList.add('hidden');
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Show/hide empty message
|
|
238
|
+
if (this.emptyMessage) {
|
|
239
|
+
if (visibleCount === 0 && normalizedQuery !== '') {
|
|
240
|
+
this.emptyMessage.classList.remove('hidden');
|
|
241
|
+
} else {
|
|
242
|
+
this.emptyMessage.classList.add('hidden');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private synchSelect() {
|
|
248
|
+
this.swapItemClasses();
|
|
249
|
+
|
|
250
|
+
// Close menu
|
|
251
|
+
return this.toggleMenu();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private updateDisplay() {
|
|
255
|
+
if (this.withTags) {
|
|
256
|
+
this.updateTags();
|
|
257
|
+
} else {
|
|
258
|
+
this.updateButtonText();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private updateButtonText() {
|
|
263
|
+
const selectedOptions = Array.from((this.select as HTMLSelectElement).options).filter(opt => opt.selected);
|
|
264
|
+
|
|
265
|
+
if (selectedOptions.length === 0) {
|
|
266
|
+
// Afficher le placeholder s'il existe
|
|
267
|
+
const placeholder = Array.from((this.select as HTMLSelectElement).options).find(opt => opt.value === '');
|
|
268
|
+
(this.buttonText as HTMLElement).innerHTML = placeholder?.innerHTML || '';
|
|
269
|
+
} else if (selectedOptions.length === 1) {
|
|
270
|
+
(this.buttonText as HTMLElement).innerHTML = selectedOptions[0].innerHTML;
|
|
271
|
+
} else {
|
|
272
|
+
(this.buttonText as HTMLElement).textContent = `${selectedOptions.length} sélectionnés`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private updateTags() {
|
|
277
|
+
if (!this.tagsContainer || !this.tagTemplate) return;
|
|
278
|
+
|
|
279
|
+
const selectedOptions = Array.from((this.select as HTMLSelectElement).options).filter(opt => opt.selected && opt.value !== '');
|
|
280
|
+
|
|
281
|
+
// Supprimer les tags existants (sauf le placeholder)
|
|
282
|
+
this.tagsContainer.querySelectorAll('[data-tag-value]').forEach(tag => tag.remove());
|
|
283
|
+
|
|
284
|
+
// Afficher/masquer le placeholder
|
|
285
|
+
if (this.placeholder) {
|
|
286
|
+
if (selectedOptions.length === 0) {
|
|
287
|
+
this.placeholder.classList.remove('hidden');
|
|
288
|
+
} else {
|
|
289
|
+
this.placeholder.classList.add('hidden');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Créer les tags
|
|
294
|
+
selectedOptions.forEach((option) => {
|
|
295
|
+
const tagFragment = this.tagTemplate!.content.cloneNode(true) as DocumentFragment;
|
|
296
|
+
const tagElement = tagFragment.querySelector('[data-tag-value]') as HTMLElement;
|
|
297
|
+
const tagText = tagFragment.querySelector('[data-dropdown=tag-text]') as HTMLElement;
|
|
298
|
+
|
|
299
|
+
tagElement.dataset.tagValue = option.value;
|
|
300
|
+
tagText.textContent = option.textContent;
|
|
301
|
+
|
|
302
|
+
this.tagsContainer?.appendChild(tagFragment);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private swapItemClasses() {
|
|
307
|
+
Array.from((this.select as HTMLSelectElement).options).forEach((option) => {
|
|
308
|
+
const item = this.itemsContainer?.querySelector(`[data-value="${option.value}"]`) as HTMLElement;
|
|
309
|
+
if (!item) return;
|
|
310
|
+
|
|
311
|
+
const checkmark = item.querySelector('[data-dropdown=item-check]') as HTMLElement;
|
|
312
|
+
|
|
313
|
+
// Récupérer les classes depuis les data-attributes de l'élément
|
|
314
|
+
const itemInactiveClasses = item.dataset.classInactive?.split(' ') || [];
|
|
315
|
+
const itemActiveClasses = item.dataset.classActive?.split(' ') || [];
|
|
316
|
+
const checkInactiveClasses = checkmark?.dataset.classInactive?.split(' ') || [];
|
|
317
|
+
const checkActiveClasses = checkmark?.dataset.classActive?.split(' ') || [];
|
|
318
|
+
|
|
319
|
+
if (option.selected) {
|
|
320
|
+
// Swap vers active
|
|
321
|
+
item.classList.remove(...itemInactiveClasses);
|
|
322
|
+
item.classList.add(...itemActiveClasses);
|
|
323
|
+
checkmark?.classList.remove(...checkInactiveClasses);
|
|
324
|
+
checkmark?.classList.add(...checkActiveClasses);
|
|
325
|
+
|
|
326
|
+
// Update ARIA
|
|
327
|
+
item.setAttribute('aria-selected', 'true');
|
|
328
|
+
this.button?.setAttribute('aria-activedescendant', item.id);
|
|
329
|
+
} else {
|
|
330
|
+
// Swap vers inactive
|
|
331
|
+
item.classList.remove(...itemActiveClasses);
|
|
332
|
+
item.classList.add(...itemInactiveClasses);
|
|
333
|
+
checkmark?.classList.remove(...checkActiveClasses);
|
|
334
|
+
checkmark?.classList.add(...checkInactiveClasses);
|
|
335
|
+
|
|
336
|
+
// Update ARIA
|
|
337
|
+
item.setAttribute('aria-selected', 'false');
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Update display (text or tags)
|
|
342
|
+
this.updateDisplay();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private preparePanel() {
|
|
346
|
+
this.logger.trace('preparePanel');
|
|
347
|
+
if (!this.select) {
|
|
348
|
+
throw new Error('Select element not found');
|
|
349
|
+
}
|
|
350
|
+
// Vider le container
|
|
351
|
+
if (this.itemsContainer) {
|
|
352
|
+
this.itemsContainer.innerHTML = '';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const options = Array.from(this.select.options);
|
|
356
|
+
options.forEach((option) => {
|
|
357
|
+
// @ts-ignore
|
|
358
|
+
const item = this.optionTemplate.content.cloneNode(true) as DocumentFragment;
|
|
359
|
+
const button = item.querySelector('button') as HTMLButtonElement;
|
|
360
|
+
|
|
361
|
+
// ARIA attributes
|
|
362
|
+
button.role = 'option';
|
|
363
|
+
button.id = `bleet-option-${option.value}-${Math.random().toString(36).substring(2, 9)}`;
|
|
364
|
+
|
|
365
|
+
// Content
|
|
366
|
+
const itemText = item.querySelector('[data-dropdown=item-text]') as HTMLElement;
|
|
367
|
+
const itemValue = item.querySelector('[data-value]') as HTMLElement;
|
|
368
|
+
itemValue.dataset.value = option.value;
|
|
369
|
+
itemText.innerHTML = option.innerHTML;
|
|
370
|
+
|
|
371
|
+
this.itemsContainer?.append(item);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Appliquer les classes active/inactive
|
|
375
|
+
this.swapItemClasses();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './burger';
|
|
2
|
+
export * from './menu';
|
|
3
|
+
export * from './badge';
|
|
4
|
+
export * from './select';
|
|
5
|
+
export * from './dropdown';
|
|
6
|
+
export * from './password';
|
|
7
|
+
export * from './tabs';
|
|
8
|
+
export * from './profile';
|
|
9
|
+
export * from './toaster-trigger';
|
|
10
|
+
export * from './alert';
|
|
11
|
+
export * from './drawer-trigger';
|
|
12
|
+
export * from './modal-trigger';
|
|
13
|
+
export * from './pager';
|
|
14
|
+
export * from './upload';
|
|
15
|
+
export * from './ajaxify-trigger';
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {customAttribute, IDisposable, IEventAggregator, ILogger, INode, IPlatform, resolve} from "aurelia";
|
|
2
|
+
import {Channels, MenuAction, MenuStatus, OverlayAction, OverlayStatus} from '../enums/event-aggregator';
|
|
3
|
+
import {IMenu, IMenuStatus, IOverlay, IOverlayStatus} from '../interfaces/event-aggregator';
|
|
4
|
+
import {ITransitionService} from '../services/transition-service';
|
|
5
|
+
import {IStorageService} from '../services/storage-service';
|
|
6
|
+
|
|
7
|
+
@customAttribute('bleet-menu')
|
|
8
|
+
export class BleetMenuCustomAttribute
|
|
9
|
+
{
|
|
10
|
+
private disposable?: IDisposable;
|
|
11
|
+
private disposableOverlay?: IDisposable;
|
|
12
|
+
private closeButton?: HTMLButtonElement;
|
|
13
|
+
private toggleButtons?: NodeListOf<HTMLButtonElement>;
|
|
14
|
+
private sublists: Map<HTMLButtonElement, {list: HTMLElement, svg: HTMLElement}> = new Map();
|
|
15
|
+
private isOpen: boolean = false;
|
|
16
|
+
public constructor(
|
|
17
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-menu'),
|
|
18
|
+
private readonly ea:IEventAggregator = resolve(IEventAggregator),
|
|
19
|
+
private readonly element: HTMLElement = resolve(INode) as HTMLElement,
|
|
20
|
+
private readonly platform: IPlatform = resolve(IPlatform),
|
|
21
|
+
private readonly transitionService: ITransitionService = resolve(ITransitionService),
|
|
22
|
+
private readonly storageService: IStorageService = resolve(IStorageService),
|
|
23
|
+
) {
|
|
24
|
+
this.logger.trace('constructor')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public attaching()
|
|
28
|
+
{
|
|
29
|
+
this.logger.trace('attaching');
|
|
30
|
+
this.closeButton = this.element.querySelector('[data-menu=close]') as HTMLButtonElement;
|
|
31
|
+
this.toggleButtons = this.element.querySelectorAll('[data-menu^="toggle-button"]');
|
|
32
|
+
this.initMenuButtons();
|
|
33
|
+
}
|
|
34
|
+
public attached()
|
|
35
|
+
{
|
|
36
|
+
this.logger.trace('attached');
|
|
37
|
+
this.disposable = this.ea.subscribe(Channels.Menu, this.onMenuEvent);
|
|
38
|
+
this.disposableOverlay = this.ea.subscribe(Channels.OverlayStatus, this.onOverlayStatus);
|
|
39
|
+
this.closeButton?.addEventListener('click', this.onClickClose);
|
|
40
|
+
this.element.addEventListener('click', this.onClickToggleButtons)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public detached()
|
|
44
|
+
{
|
|
45
|
+
this.logger.trace('detached');
|
|
46
|
+
this.closeButton?.removeEventListener('click', this.onClickClose);
|
|
47
|
+
this.element.removeEventListener('click', this.onClickToggleButtons)
|
|
48
|
+
this.disposableOverlay?.dispose();
|
|
49
|
+
this.disposable?.dispose();
|
|
50
|
+
}
|
|
51
|
+
public dispose() {
|
|
52
|
+
this.logger.trace('dispose');
|
|
53
|
+
this.disposableOverlay?.dispose();
|
|
54
|
+
this.disposable?.dispose();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private open(fromOverlay: boolean = false) {
|
|
58
|
+
if (!this.isOpen) {
|
|
59
|
+
this.logger.trace('open');
|
|
60
|
+
this.isOpen = true;
|
|
61
|
+
this.transitionService.run(this.element, (element: HTMLElement) => {
|
|
62
|
+
if (!fromOverlay) {
|
|
63
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{action: OverlayAction.Open});
|
|
64
|
+
}
|
|
65
|
+
this.ea.publish(Channels.MenuStatus, <IMenuStatus>{status: MenuStatus.Opening});
|
|
66
|
+
element.classList.add('translate-x-0');
|
|
67
|
+
element.classList.remove('-translate-x-full');
|
|
68
|
+
element.ariaHidden = 'false';
|
|
69
|
+
}, (element: HTMLElement) => {
|
|
70
|
+
this.ea.publish(Channels.MenuStatus, <IMenuStatus>{status: MenuStatus.Opened});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
private close(fromOverlay: boolean = false) {
|
|
75
|
+
if (this.isOpen) {
|
|
76
|
+
this.logger.trace('close');
|
|
77
|
+
this.isOpen = false;
|
|
78
|
+
this.transitionService.run(this.element, (element: HTMLElement) => {
|
|
79
|
+
this.ea.publish(Channels.MenuStatus, <IMenuStatus>{status: MenuStatus.Closing});
|
|
80
|
+
if (!fromOverlay) {
|
|
81
|
+
this.ea.publish(Channels.Overlay, <IOverlay>{action: OverlayAction.Close});
|
|
82
|
+
}
|
|
83
|
+
element.classList.add('-translate-x-full');
|
|
84
|
+
element.classList.remove('translate-x-0');
|
|
85
|
+
element.ariaHidden = 'true';
|
|
86
|
+
}, (element: HTMLElement) => {
|
|
87
|
+
this.ea.publish(Channels.MenuStatus, <IMenuStatus>{status: MenuStatus.Closed});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private onClickClose = (event: MouseEvent) => {
|
|
93
|
+
this.logger.trace('onClickClose', event);
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
this.close();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private onClickToggleButtons = (event: MouseEvent) => {
|
|
99
|
+
const target = event.target as HTMLElement;
|
|
100
|
+
const btn = target.closest('[data-menu^="toggle-button"]') as HTMLButtonElement;
|
|
101
|
+
if (btn && btn.matches('[data-menu^="toggle-button"]')) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
this.toggleButton(btn);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private initMenuButtons() {
|
|
108
|
+
this.logger.trace('initMenu');
|
|
109
|
+
this.toggleButtons?.forEach((btn: HTMLButtonElement) => {
|
|
110
|
+
if (!this.sublists.has(btn)) {
|
|
111
|
+
const id = btn.dataset.menu?.replace('toggle-button-', '');
|
|
112
|
+
const isOpen = this.storageService.load(`toggle-list-${id}`, false);
|
|
113
|
+
const list = this.element.querySelector(`[data-menu="toggle-list-${id}"]`) as HTMLUListElement;
|
|
114
|
+
const svg = btn.querySelector('svg[data-menu=icon]') as HTMLElement;
|
|
115
|
+
this.sublists.set(btn, {list, svg});
|
|
116
|
+
if (!isOpen) {
|
|
117
|
+
svg.classList.remove('rotate-180');
|
|
118
|
+
list.classList.add('hidden');
|
|
119
|
+
btn.ariaExpanded = 'false';
|
|
120
|
+
} else {
|
|
121
|
+
svg.classList.add('rotate-180');
|
|
122
|
+
list.classList.remove('hidden');
|
|
123
|
+
btn.ariaExpanded = 'true';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private toggleButton(btn: HTMLButtonElement) {
|
|
130
|
+
if (this.sublists.has(btn)) {
|
|
131
|
+
const sublist = this.sublists.get(btn);
|
|
132
|
+
const id = btn.dataset.menu?.replace('toggle-button-', '');
|
|
133
|
+
if (sublist?.list.classList.contains('hidden')) {
|
|
134
|
+
sublist?.list.classList.remove('hidden');
|
|
135
|
+
sublist?.svg.classList.add('rotate-180');
|
|
136
|
+
btn.ariaExpanded = 'true';
|
|
137
|
+
this.storageService.save(`toggle-list-${id}`, true);
|
|
138
|
+
} else {
|
|
139
|
+
sublist?.list.classList.add('hidden');
|
|
140
|
+
sublist?.svg.classList.remove('rotate-180');
|
|
141
|
+
btn.ariaExpanded = 'false';
|
|
142
|
+
this.storageService.save(`toggle-list-${id}`, false);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
private closeOtherButtons(except: HTMLButtonElement) {
|
|
147
|
+
this.sublists.forEach((value, key) => {
|
|
148
|
+
if (key !== except) {
|
|
149
|
+
const id = key.dataset.menu?.replace('toggle-button-', '');
|
|
150
|
+
this.storageService.save(`toggle-list-${id}`, false);
|
|
151
|
+
value.list.classList.add('hidden');
|
|
152
|
+
value.svg.classList.remove('rotate-180');
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
private onMenuEvent = (data: IMenu) => {
|
|
157
|
+
this.logger.trace('onMenuEvent', data);
|
|
158
|
+
if (data.action === MenuAction.Close) {
|
|
159
|
+
// this.element.classList.add('-translate-x-full');
|
|
160
|
+
this.logger.trace('Menu Close action received');
|
|
161
|
+
this.close();
|
|
162
|
+
} else if (data.action === MenuAction.Open) {
|
|
163
|
+
// this.element.classList.remove('-translate-x-full');
|
|
164
|
+
this.logger.trace('Menu Open action received');
|
|
165
|
+
this.open();
|
|
166
|
+
} else if (data.action === MenuAction.Toggle) {
|
|
167
|
+
// this.element.classList.toggle('-translate-x-full');
|
|
168
|
+
this.logger.trace('Menu Toggle action received');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
private onOverlayStatus = (data: IOverlayStatus) => {
|
|
172
|
+
if (data.status === OverlayStatus.Closing) {
|
|
173
|
+
this.logger.trace('Overlay Close action received');
|
|
174
|
+
this.close(true);
|
|
175
|
+
} else {
|
|
176
|
+
this.logger.trace('onOverlayStatus unhandled', data);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {bindable, customAttribute, IEventAggregator, ILogger, INode, resolve} from "aurelia";
|
|
2
|
+
import {Channels, ModalAction} from '../enums/event-aggregator';
|
|
3
|
+
import {IModal} from '../interfaces/event-aggregator';
|
|
4
|
+
|
|
5
|
+
@customAttribute({ name: 'bleet-modal-trigger', defaultProperty: 'id' })
|
|
6
|
+
export class BleetModalTriggerCustomAttribute {
|
|
7
|
+
|
|
8
|
+
@bindable id: string = '';
|
|
9
|
+
@bindable() url: string = '';
|
|
10
|
+
@bindable() color: string = 'primary';
|
|
11
|
+
|
|
12
|
+
public constructor(
|
|
13
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-modal-trigger'),
|
|
14
|
+
private readonly element: HTMLElement = resolve(INode) as HTMLElement,
|
|
15
|
+
private readonly ea: IEventAggregator = resolve(IEventAggregator),
|
|
16
|
+
) {
|
|
17
|
+
this.logger.trace('constructor')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public attaching()
|
|
21
|
+
{
|
|
22
|
+
this.logger.trace('attaching');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public attached()
|
|
26
|
+
{
|
|
27
|
+
this.logger.trace('attached');
|
|
28
|
+
this.element.addEventListener('click', this.onClick);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public detached()
|
|
32
|
+
{
|
|
33
|
+
this.logger.trace('detached');
|
|
34
|
+
this.element.removeEventListener('click', this.onClick);
|
|
35
|
+
}
|
|
36
|
+
public dispose()
|
|
37
|
+
{
|
|
38
|
+
this.logger.trace('dispose');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private onClick = (event: Event): void => {
|
|
42
|
+
this.logger.trace('onClick', event);
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
|
|
45
|
+
this.ea.publish(Channels.Modal, <IModal>{
|
|
46
|
+
action: ModalAction.Toggle,
|
|
47
|
+
id: this.id,
|
|
48
|
+
url: this.url,
|
|
49
|
+
color: this.color,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {customAttribute, ILogger, INode, IPlatform, resolve} from "aurelia";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@customAttribute('bleet-pager')
|
|
5
|
+
export class BleetPagerCustomAttribute {
|
|
6
|
+
|
|
7
|
+
private select?: HTMLSelectElement;
|
|
8
|
+
public constructor(
|
|
9
|
+
private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-pager'),
|
|
10
|
+
private readonly element: HTMLElement = resolve(INode) as HTMLElement,
|
|
11
|
+
private readonly p: IPlatform = resolve(IPlatform),
|
|
12
|
+
) {
|
|
13
|
+
this.logger.trace('constructor')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public attaching()
|
|
17
|
+
{
|
|
18
|
+
this.logger.trace('attaching');
|
|
19
|
+
this.select = this.element.querySelector('[data-pager="select"]') as HTMLSelectElement;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public attached()
|
|
23
|
+
{
|
|
24
|
+
this.logger.trace('attached');
|
|
25
|
+
this.select?.addEventListener('change', this.onChangeSelect);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public detached()
|
|
29
|
+
{
|
|
30
|
+
this.logger.trace('detached');
|
|
31
|
+
this.select?.removeEventListener('change', this.onChangeSelect);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private onChangeSelect = (event: Event) => {
|
|
35
|
+
this.logger.trace('onChangeSelect', event);
|
|
36
|
+
const pageNumber = this.select?.value;
|
|
37
|
+
const link = this.element.querySelector(`[data-pager="page-${pageNumber}"]`) as HTMLElement;
|
|
38
|
+
|
|
39
|
+
if (link) {
|
|
40
|
+
link.click();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|