@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.
Files changed (157) hide show
  1. package/blackcube-aurelia2-bleet-1.0.0.tgz +0 -0
  2. package/dist/index.es.js +4514 -0
  3. package/dist/index.es.js.map +1 -0
  4. package/dist/index.js +4549 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/types/attributes/ajaxify-trigger.d.ts +36 -0
  7. package/dist/types/attributes/ajaxify-trigger.d.ts.map +1 -0
  8. package/dist/types/attributes/alert.d.ts +15 -0
  9. package/dist/types/attributes/alert.d.ts.map +1 -0
  10. package/dist/types/attributes/badge.d.ts +13 -0
  11. package/dist/types/attributes/badge.d.ts.map +1 -0
  12. package/dist/types/attributes/burger.d.ts +11 -0
  13. package/dist/types/attributes/burger.d.ts.map +1 -0
  14. package/dist/types/attributes/drawer-trigger.d.ts +16 -0
  15. package/dist/types/attributes/drawer-trigger.d.ts.map +1 -0
  16. package/dist/types/attributes/dropdown.d.ts +38 -0
  17. package/dist/types/attributes/dropdown.d.ts.map +1 -0
  18. package/dist/types/attributes/index.d.ts +16 -0
  19. package/dist/types/attributes/index.d.ts.map +1 -0
  20. package/dist/types/attributes/menu.d.ts +32 -0
  21. package/dist/types/attributes/menu.d.ts.map +1 -0
  22. package/dist/types/attributes/modal-trigger.d.ts +16 -0
  23. package/dist/types/attributes/modal-trigger.d.ts.map +1 -0
  24. package/dist/types/attributes/pager.d.ts +13 -0
  25. package/dist/types/attributes/pager.d.ts.map +1 -0
  26. package/dist/types/attributes/password.d.ts +15 -0
  27. package/dist/types/attributes/password.d.ts.map +1 -0
  28. package/dist/types/attributes/profile.d.ts +24 -0
  29. package/dist/types/attributes/profile.d.ts.map +1 -0
  30. package/dist/types/attributes/select.d.ts +24 -0
  31. package/dist/types/attributes/select.d.ts.map +1 -0
  32. package/dist/types/attributes/tabs.d.ts +16 -0
  33. package/dist/types/attributes/tabs.d.ts.map +1 -0
  34. package/dist/types/attributes/toaster-trigger.d.ts +19 -0
  35. package/dist/types/attributes/toaster-trigger.d.ts.map +1 -0
  36. package/dist/types/attributes/upload.d.ts +57 -0
  37. package/dist/types/attributes/upload.d.ts.map +1 -0
  38. package/dist/types/codecs/ajaxify-codec.d.ts +5 -0
  39. package/dist/types/codecs/ajaxify-codec.d.ts.map +1 -0
  40. package/dist/types/codecs/csrf-codec.d.ts +7 -0
  41. package/dist/types/codecs/csrf-codec.d.ts.map +1 -0
  42. package/dist/types/codecs/request-codec.d.ts +5 -0
  43. package/dist/types/codecs/request-codec.d.ts.map +1 -0
  44. package/dist/types/components/bleet-ajaxify.d.ts +17 -0
  45. package/dist/types/components/bleet-ajaxify.d.ts.map +1 -0
  46. package/dist/types/components/bleet-ajaxify.html.d.ts +3 -0
  47. package/dist/types/components/bleet-ajaxify.html.d.ts.map +1 -0
  48. package/dist/types/components/bleet-drawer.d.ts +40 -0
  49. package/dist/types/components/bleet-drawer.d.ts.map +1 -0
  50. package/dist/types/components/bleet-drawer.html.d.ts +3 -0
  51. package/dist/types/components/bleet-drawer.html.d.ts.map +1 -0
  52. package/dist/types/components/bleet-modal.d.ts +46 -0
  53. package/dist/types/components/bleet-modal.d.ts.map +1 -0
  54. package/dist/types/components/bleet-modal.html.d.ts +3 -0
  55. package/dist/types/components/bleet-modal.html.d.ts.map +1 -0
  56. package/dist/types/components/bleet-overlay.d.ts +21 -0
  57. package/dist/types/components/bleet-overlay.d.ts.map +1 -0
  58. package/dist/types/components/bleet-quilljs.d.ts +19 -0
  59. package/dist/types/components/bleet-quilljs.d.ts.map +1 -0
  60. package/dist/types/components/bleet-quilljs.html.d.ts +3 -0
  61. package/dist/types/components/bleet-quilljs.html.d.ts.map +1 -0
  62. package/dist/types/components/bleet-toast.d.ts +26 -0
  63. package/dist/types/components/bleet-toast.d.ts.map +1 -0
  64. package/dist/types/components/bleet-toast.html.d.ts +3 -0
  65. package/dist/types/components/bleet-toast.html.d.ts.map +1 -0
  66. package/dist/types/components/bleet-toaster-trigger.d.ts +20 -0
  67. package/dist/types/components/bleet-toaster-trigger.d.ts.map +1 -0
  68. package/dist/types/components/bleet-toaster.d.ts +15 -0
  69. package/dist/types/components/bleet-toaster.d.ts.map +1 -0
  70. package/dist/types/components/bleet-toaster.html.d.ts +3 -0
  71. package/dist/types/components/bleet-toaster.html.d.ts.map +1 -0
  72. package/dist/types/components/index.d.ts +9 -0
  73. package/dist/types/components/index.d.ts.map +1 -0
  74. package/dist/types/configure.d.ts +35 -0
  75. package/dist/types/configure.d.ts.map +1 -0
  76. package/dist/types/enums/api.d.ts +11 -0
  77. package/dist/types/enums/api.d.ts.map +1 -0
  78. package/dist/types/enums/event-aggregator.d.ts +123 -0
  79. package/dist/types/enums/event-aggregator.d.ts.map +1 -0
  80. package/dist/types/index.d.ts +26 -0
  81. package/dist/types/index.d.ts.map +1 -0
  82. package/dist/types/interfaces/api.d.ts +56 -0
  83. package/dist/types/interfaces/api.d.ts.map +1 -0
  84. package/dist/types/interfaces/dialog.d.ts +18 -0
  85. package/dist/types/interfaces/dialog.d.ts.map +1 -0
  86. package/dist/types/interfaces/event-aggregator.d.ts +75 -0
  87. package/dist/types/interfaces/event-aggregator.d.ts.map +1 -0
  88. package/dist/types/services/api-service.d.ts +64 -0
  89. package/dist/types/services/api-service.d.ts.map +1 -0
  90. package/dist/types/services/http-service.d.ts +22 -0
  91. package/dist/types/services/http-service.d.ts.map +1 -0
  92. package/dist/types/services/socketio-service.d.ts +23 -0
  93. package/dist/types/services/socketio-service.d.ts.map +1 -0
  94. package/dist/types/services/storage-service.d.ts +13 -0
  95. package/dist/types/services/storage-service.d.ts.map +1 -0
  96. package/dist/types/services/svg-service.d.ts +17 -0
  97. package/dist/types/services/svg-service.d.ts.map +1 -0
  98. package/dist/types/services/transition-service.d.ts +13 -0
  99. package/dist/types/services/transition-service.d.ts.map +1 -0
  100. package/dist/types/services/trap-focus-service.d.ts +28 -0
  101. package/dist/types/services/trap-focus-service.d.ts.map +1 -0
  102. package/doc/bleet-api-reference.md +1333 -0
  103. package/doc/bleet-model-api-reference.md +379 -0
  104. package/doc/bleet-typescript-api-reference.md +1037 -0
  105. package/package.json +43 -0
  106. package/resource.d.ts +22 -0
  107. package/src/attributes/ajaxify-trigger.ts +218 -0
  108. package/src/attributes/alert.ts +55 -0
  109. package/src/attributes/badge.ts +39 -0
  110. package/src/attributes/burger.ts +36 -0
  111. package/src/attributes/drawer-trigger.ts +53 -0
  112. package/src/attributes/dropdown.ts +377 -0
  113. package/src/attributes/index.ts +15 -0
  114. package/src/attributes/menu.ts +179 -0
  115. package/src/attributes/modal-trigger.ts +53 -0
  116. package/src/attributes/pager.ts +43 -0
  117. package/src/attributes/password.ts +47 -0
  118. package/src/attributes/profile.ts +112 -0
  119. package/src/attributes/select.ts +214 -0
  120. package/src/attributes/tabs.ts +99 -0
  121. package/src/attributes/toaster-trigger.ts +54 -0
  122. package/src/attributes/upload.ts +380 -0
  123. package/src/codecs/ajaxify-codec.ts +16 -0
  124. package/src/codecs/csrf-codec.ts +41 -0
  125. package/src/codecs/request-codec.ts +16 -0
  126. package/src/components/bleet-ajaxify.html.ts +4 -0
  127. package/src/components/bleet-ajaxify.ts +62 -0
  128. package/src/components/bleet-drawer.html.ts +36 -0
  129. package/src/components/bleet-drawer.ts +236 -0
  130. package/src/components/bleet-modal.html.ts +30 -0
  131. package/src/components/bleet-modal.ts +274 -0
  132. package/src/components/bleet-overlay.ts +111 -0
  133. package/src/components/bleet-quilljs.html.ts +4 -0
  134. package/src/components/bleet-quilljs.ts +73 -0
  135. package/src/components/bleet-toast.html.ts +44 -0
  136. package/src/components/bleet-toast.ts +133 -0
  137. package/src/components/bleet-toaster-trigger.ts +66 -0
  138. package/src/components/bleet-toaster.html.ts +11 -0
  139. package/src/components/bleet-toaster.ts +72 -0
  140. package/src/components/index.ts +8 -0
  141. package/src/configure.ts +121 -0
  142. package/src/enums/api.ts +12 -0
  143. package/src/enums/event-aggregator.ts +131 -0
  144. package/src/index.ts +220 -0
  145. package/src/interfaces/api.ts +64 -0
  146. package/src/interfaces/dialog.ts +25 -0
  147. package/src/interfaces/event-aggregator.ts +88 -0
  148. package/src/services/api-service.ts +387 -0
  149. package/src/services/http-service.ts +166 -0
  150. package/src/services/socketio-service.ts +138 -0
  151. package/src/services/storage-service.ts +36 -0
  152. package/src/services/svg-service.ts +35 -0
  153. package/src/services/transition-service.ts +39 -0
  154. package/src/services/trap-focus-service.ts +213 -0
  155. package/src/types/css.d.ts +4 -0
  156. package/src/types/html.d.ts +12 -0
  157. package/src/types/svg.d.ts +4 -0
@@ -0,0 +1,47 @@
1
+ import {customAttribute, ILogger, INode, resolve} from "aurelia";
2
+
3
+ @customAttribute('bleet-password')
4
+ export class BleetPasswordCustomAttribute
5
+ {
6
+ private button?: HTMLElement;
7
+ private iconHidden?: HTMLElement;
8
+ private iconVisible?: HTMLElement;
9
+ private input?: HTMLInputElement;
10
+
11
+ public constructor(
12
+ private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-password'),
13
+ private readonly element: HTMLElement = resolve(INode) as HTMLElement,
14
+ ) {
15
+ }
16
+
17
+ public attaching(): void
18
+ {
19
+ this.button = this.element.querySelector('[data-password=toggle]') ?? undefined;
20
+ this.iconHidden = this.button?.querySelector('[data-password=icon-hidden]') ?? undefined;
21
+ this.iconVisible = this.button?.querySelector('[data-password=icon-visible]') ?? undefined;
22
+ this.input = this.element.querySelector('input') ?? undefined;
23
+ }
24
+
25
+ public attached(): void
26
+ {
27
+ this.button?.addEventListener('click', this.onToggle);
28
+ }
29
+
30
+ public detaching(): void
31
+ {
32
+ this.button?.removeEventListener('click', this.onToggle);
33
+ }
34
+
35
+ private onToggle = (event: MouseEvent): void => {
36
+ event.preventDefault();
37
+
38
+ const isPassword = this.input?.type === 'password';
39
+
40
+ if (this.input) {
41
+ this.input.type = isPassword ? 'text' : 'password';
42
+ }
43
+
44
+ this.iconHidden?.classList.toggle('hidden', isPassword);
45
+ this.iconVisible?.classList.toggle('hidden', !isPassword);
46
+ }
47
+ }
@@ -0,0 +1,112 @@
1
+ import {bindable, customAttribute, IEventAggregator, ILogger, INode, IPlatform, resolve} from "aurelia";
2
+ import {Channels, ProfileAction, ProfileStatus} from '../enums/event-aggregator';
3
+ import {IProfile, IProfileStatus} from '../interfaces/event-aggregator';
4
+ import {ITransitionService} from '../services/transition-service';
5
+ import {ITrapFocusService} from '../services/trap-focus-service';
6
+
7
+ @customAttribute({ name: 'bleet-profile', defaultProperty: 'id' })
8
+ export class BleetProfileCustomAttribute
9
+ {
10
+
11
+ @bindable id: string = '';
12
+ private toggleButton?: HTMLButtonElement;
13
+ private panel?: HTMLDivElement;
14
+ private isOpen: boolean = false;
15
+
16
+ public constructor(
17
+ private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-profile'),
18
+ private readonly ea: IEventAggregator = resolve(IEventAggregator),
19
+ private readonly element: HTMLElement = resolve(INode) as HTMLElement,
20
+ private readonly transitionService: ITransitionService = resolve(ITransitionService),
21
+ private readonly platform: IPlatform = resolve(IPlatform),
22
+ private readonly trapFocusService: ITrapFocusService = resolve(ITrapFocusService),
23
+ ) {
24
+ this.logger.trace('constructor')
25
+ }
26
+
27
+ public attaching()
28
+ {
29
+ this.logger.trace('attaching');
30
+ this.toggleButton = this.element.querySelector('[data-profile=toggle]') as HTMLButtonElement;
31
+ this.panel = this.element.querySelector('[data-profile=panel]') as HTMLDivElement;
32
+ }
33
+
34
+ public attached()
35
+ {
36
+ this.logger.trace('attached');
37
+ this.toggleButton?.addEventListener('click', this.onClickToggle);
38
+ }
39
+
40
+ public detached()
41
+ {
42
+ this.logger.trace('detached');
43
+ this.toggleButton?.removeEventListener('click', this.onClickToggle);
44
+ if (this.isOpen) {
45
+ this.trapFocusService.stop();
46
+ }
47
+ }
48
+
49
+ private onClickToggle = (event: MouseEvent) => {
50
+ this.logger.trace('onClickToggle', event);
51
+ event.preventDefault();
52
+ if (this.isOpen) {
53
+ this.close();
54
+ } else {
55
+ this.open();
56
+ }
57
+ }
58
+
59
+ private onStopTrapFocus = () => {
60
+ this.logger.trace('onStopTrapFocus');
61
+ this.isOpen = false;
62
+ this.toggleButton?.setAttribute('aria-expanded', 'false');
63
+ this.transitionService.run(this.panel!, (element) => {
64
+ this.ea.publish(Channels.Profile, <IProfile>{action: ProfileAction.Close, id: this.id});
65
+ this.ea.publish(Channels.ProfileStatus, <IProfileStatus>{status: ProfileStatus.Closing, id: this.id});
66
+ this.platform.requestAnimationFrame(() => {
67
+ element.classList.remove('opacity-100', 'scale-100');
68
+ element.classList.add('opacity-0', 'scale-95', 'pointer-events-none');
69
+ });
70
+ }, (element) => {
71
+ element.classList.add('hidden');
72
+ this.ea.publish(Channels.ProfileStatus, <IProfileStatus>{status: ProfileStatus.Closed, id: this.id});
73
+ });
74
+ return Promise.resolve();
75
+ }
76
+
77
+ private open() {
78
+ this.logger.trace('open');
79
+ this.isOpen = true;
80
+ this.toggleButton?.setAttribute('aria-expanded', 'true');
81
+
82
+ // Find first focusable item in panel
83
+ const firstItem = this.panel?.querySelector('a, button') as HTMLElement;
84
+
85
+ this.trapFocusService.start(
86
+ this.toggleButton as HTMLButtonElement,
87
+ this.panel as HTMLElement,
88
+ this.element,
89
+ undefined,
90
+ this.onStopTrapFocus,
91
+ firstItem
92
+ ).then(() => {
93
+ this.transitionService.run(this.panel!, (element) => {
94
+ this.ea.publish(Channels.Profile, <IProfile>{action: ProfileAction.Open, id: this.id});
95
+ this.ea.publish(Channels.ProfileStatus, <IProfileStatus>{status: ProfileStatus.Opening, id: this.id});
96
+ element.classList.remove('hidden');
97
+ this.platform.requestAnimationFrame(() => {
98
+ element.classList.add('opacity-100', 'scale-100');
99
+ element.classList.remove('opacity-0', 'scale-95', 'pointer-events-none');
100
+ });
101
+ }, () => {
102
+ this.ea.publish(Channels.ProfileStatus, <IProfileStatus>{status: ProfileStatus.Opened, id: this.id});
103
+ });
104
+ });
105
+ }
106
+
107
+ private close() {
108
+ this.logger.trace('close');
109
+ this.trapFocusService.stop();
110
+ }
111
+
112
+ }
@@ -0,0 +1,214 @@
1
+ import {customAttribute, ILogger, INode, resolve} from "aurelia";
2
+ import {ITrapFocusService} from '../services/trap-focus-service';
3
+
4
+ // @ts-ignore
5
+ @customAttribute('bleet-select')
6
+ export class BleetSelectCustomAttribute {
7
+
8
+ private select?: HTMLSelectElement;
9
+ private button?: HTMLButtonElement;
10
+ private buttonText?: HTMLElement;
11
+ private optionTemplate?: HTMLTemplateElement;
12
+ private itemsPlace?: HTMLElement;
13
+
14
+ public constructor(
15
+ private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-select'),
16
+ private readonly element: HTMLElement = resolve(INode) as HTMLElement,
17
+ private readonly trapFocusService: ITrapFocusService = resolve(ITrapFocusService),
18
+ ) {
19
+ this.logger.trace('constructor')
20
+ }
21
+
22
+ public attaching()
23
+ {
24
+ this.logger.trace('attaching');
25
+ this.select = this.element.querySelector('select') as HTMLSelectElement;
26
+ this.button = this.element.querySelector('button') as HTMLButtonElement;
27
+ this.buttonText = this.button.querySelector('[data-select=value]') as HTMLElement;
28
+ this.optionTemplate = this.element.querySelector('[data-select=item-template]') as HTMLTemplateElement;
29
+ this.itemsPlace = this.element.querySelector('[data-select=items]') as HTMLElement;
30
+ }
31
+
32
+ public attached()
33
+ {
34
+ this.logger.trace('attached');
35
+ if (!this.itemsPlace) {
36
+ throw new Error('Items place element not found');
37
+ }
38
+ if (!this.itemsPlace.id) {
39
+ this.itemsPlace.id = `data-select-items-${Math.random().toString(36).substring(2, 15)}`;
40
+ }
41
+ if (!this.select?.options) {
42
+ throw new Error('Select options not found');
43
+ }
44
+ if (!this.optionTemplate) {
45
+ throw new Error('Option template not found');
46
+ }
47
+ if (!this.button) {
48
+ throw new Error('Button element not found');
49
+ }
50
+ if (!this.buttonText) {
51
+ throw new Error('Button text element not found');
52
+ }
53
+ this.preparePanel();
54
+
55
+ // ARIA setup
56
+ this.button.ariaHasPopup = 'listbox';
57
+ this.button.setAttribute('aria-controls', this.itemsPlace.id);
58
+ this.itemsPlace.role = 'listbox';
59
+
60
+ // Event listeners
61
+ this.button?.addEventListener('click', this.onClickToggleMenu);
62
+ this.itemsPlace?.addEventListener('click', this.onClickToggleItem);
63
+ }
64
+
65
+ public detached()
66
+ {
67
+ this.logger.trace('detached');
68
+ this.button?.removeEventListener('click', this.onClickToggleMenu);
69
+ this.itemsPlace?.removeEventListener('click', this.onClickToggleItem);
70
+ }
71
+
72
+ private onClickToggleMenu = (event: MouseEvent) => {
73
+ this.logger.trace('onClick', event);
74
+ event.preventDefault();
75
+ if (this.select?.disabled) {
76
+ return;
77
+ }
78
+ return this.toggleMenu();
79
+ }
80
+
81
+ private onStopTrapFocus = () => {
82
+ this.logger.trace('onStopTrapFocus');
83
+ this.itemsPlace?.classList.add('hidden');
84
+ return Promise.resolve();
85
+ }
86
+
87
+ private toggleMenu = () => {
88
+ const isClosed = this.itemsPlace?.classList.contains('hidden');
89
+ return new Promise((resolve) => {
90
+ if (isClosed) {
91
+ // Opening menu
92
+ (this.button as HTMLButtonElement).ariaExpanded = 'true';
93
+
94
+ // Find selected option to focus initially
95
+ const selectedOption = this.itemsPlace?.querySelector('[aria-selected="true"]') as HTMLElement;
96
+
97
+ return this.trapFocusService.start(
98
+ this.button as HTMLButtonElement,
99
+ this.itemsPlace as HTMLElement,
100
+ this.element,
101
+ undefined,
102
+ this.onStopTrapFocus,
103
+ selectedOption // Pass selected option as initial focus
104
+ )
105
+ .then(() => {
106
+ this.logger.trace('toggleMenu opened');
107
+ this.itemsPlace?.classList.remove('hidden');
108
+ resolve(void 0);
109
+ });
110
+ } else {
111
+ // Closing menu
112
+ (this.button as HTMLButtonElement).ariaExpanded = 'false';
113
+ return this.trapFocusService.stop()
114
+ .then(() => {
115
+ this.logger.trace('toggleMenu closed');
116
+ this.itemsPlace?.classList.add('hidden');
117
+ resolve(void 0);
118
+ });
119
+ }
120
+ });
121
+ }
122
+
123
+ private onClickToggleItem = (event: MouseEvent) => {
124
+ this.logger.trace('onClickItem', event);
125
+ event.preventDefault();
126
+ const element = (event.target as HTMLElement).closest('[data-value]') as HTMLElement;
127
+
128
+ // Update select options
129
+ Array.from((this.select as HTMLSelectElement).options).forEach((option) => {
130
+ option.selected = option.value == element.dataset.value;
131
+ });
132
+
133
+ // Dispatch change event sur le select natif pour active-form
134
+ this.select?.dispatchEvent(new Event('change', { bubbles: true }));
135
+
136
+ this.synchSelect();
137
+ }
138
+
139
+ private synchSelect() {
140
+ this.swapItemClasses();
141
+
142
+ // Close menu
143
+ return this.toggleMenu();
144
+ }
145
+
146
+ private swapItemClasses() {
147
+ Array.from((this.select as HTMLSelectElement).options).forEach((option) => {
148
+ const item = this.itemsPlace?.querySelector(`[data-value="${option.value}"]`) as HTMLElement;
149
+ if (!item) return;
150
+
151
+ const checkmark = item.querySelector('[data-select=item-check]') as HTMLElement;
152
+
153
+ // Récupérer les classes depuis les data-attributes de l'élément
154
+ const itemInactiveClasses = item.dataset.classInactive?.split(' ') || [];
155
+ const itemActiveClasses = item.dataset.classActive?.split(' ') || [];
156
+ const checkInactiveClasses = checkmark?.dataset.classInactive?.split(' ') || [];
157
+ const checkActiveClasses = checkmark?.dataset.classActive?.split(' ') || [];
158
+
159
+ if (option.selected) {
160
+ // Swap vers active
161
+ item.classList.remove(...itemInactiveClasses);
162
+ item.classList.add(...itemActiveClasses);
163
+ checkmark?.classList.remove(...checkInactiveClasses);
164
+ checkmark?.classList.add(...checkActiveClasses);
165
+
166
+ // Update ARIA
167
+ item.setAttribute('aria-selected', 'true');
168
+ this.button?.setAttribute('aria-activedescendant', item.id);
169
+
170
+ // Update button text
171
+ (this.buttonText as HTMLElement).innerHTML = option.innerHTML;
172
+ } else {
173
+ // Swap vers inactive
174
+ item.classList.remove(...itemActiveClasses);
175
+ item.classList.add(...itemInactiveClasses);
176
+ checkmark?.classList.remove(...checkActiveClasses);
177
+ checkmark?.classList.add(...checkInactiveClasses);
178
+
179
+ // Update ARIA
180
+ item.setAttribute('aria-selected', 'false');
181
+ }
182
+ });
183
+ }
184
+ private preparePanel() {
185
+ this.logger.trace('preparePanel');
186
+ if (!this.select) {
187
+ throw new Error('Select element not found');
188
+ }
189
+ // Vider le panel (sauf les templates)
190
+ this.itemsPlace?.querySelectorAll('button').forEach((child) => child.remove());
191
+
192
+ const options = Array.from(this.select.options);
193
+ options.forEach((option) => {
194
+ // @ts-ignore
195
+ const item = this.optionTemplate.content.cloneNode(true) as DocumentFragment;
196
+ const button = item.querySelector('button') as HTMLButtonElement;
197
+
198
+ // ARIA attributes
199
+ button.role = 'option';
200
+ button.id = `bleet-option-${option.value}-${Math.random().toString(36).substring(2, 9)}`;
201
+
202
+ // Content
203
+ const itemText = item.querySelector('[data-select=item-text]') as HTMLElement;
204
+ const itemValue = item.querySelector('[data-value]') as HTMLElement;
205
+ itemValue.dataset.value = option.value;
206
+ itemText.innerHTML = option.innerHTML;
207
+
208
+ this.itemsPlace?.append(item);
209
+ });
210
+
211
+ // Appliquer les classes active/inactive
212
+ this.swapItemClasses();
213
+ }
214
+ }
@@ -0,0 +1,99 @@
1
+ import {customAttribute, ILogger, INode, IPlatform, resolve} from "aurelia";
2
+
3
+
4
+ @customAttribute('bleet-tabs')
5
+ export class BleetTabsCustomAttribute {
6
+
7
+ private activeClasses: string = '';
8
+ private inactiveClasses: string = '';
9
+ private select?: HTMLSelectElement;
10
+ public constructor(
11
+ private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-tabs'),
12
+ private readonly element: HTMLElement = resolve(INode) as HTMLElement,
13
+ private readonly p: IPlatform = resolve(IPlatform),
14
+ ) {
15
+ this.logger.trace('constructor')
16
+ }
17
+
18
+ public attaching()
19
+ {
20
+ this.logger.trace('attaching');
21
+ const activeButton = this.element.querySelector('[data-tabs^="tab-"][aria-selected="true"]');
22
+ const inactiveButton = this.element.querySelector('[data-tabs^="tab-"][aria-selected="false"]');
23
+
24
+ this.activeClasses = activeButton?.className || '';
25
+ this.inactiveClasses = inactiveButton?.className || '';
26
+
27
+ this.select = this.element.querySelector('select') as HTMLSelectElement;
28
+ }
29
+
30
+ public attached()
31
+ {
32
+ this.logger.trace('attached');
33
+ this.element.querySelectorAll('[data-tabs^="tab-"]').forEach((button: Element) => {
34
+ button.addEventListener('click', this.onClickTab);
35
+ });
36
+
37
+ // Écouter le select mobile
38
+ if (this.select) {
39
+ this.select.addEventListener('change', this.onChangeSelect);
40
+ }
41
+ }
42
+
43
+ public detached()
44
+ {
45
+ this.logger.trace('detached');
46
+ this.element.querySelectorAll('[data-tabs^="tab-"]').forEach((button: Element) => {
47
+ button.removeEventListener('click', this.onClickTab);
48
+ });
49
+
50
+ if (this.select) {
51
+ this.select.removeEventListener('change', this.onChangeSelect);
52
+ }
53
+ }
54
+
55
+ private onClickTab = (event: Event) => {
56
+ this.logger.trace('onClickTab', event);
57
+ event.preventDefault();
58
+ const tabIndex = (event.currentTarget as HTMLElement).getAttribute('data-tabs')?.replace('tab-', '') || '0';
59
+
60
+ // Mettre à jour les tabs
61
+ this.element.querySelectorAll('[data-tabs^="tab-"]').forEach((button: Element) => {
62
+ const buttonTabIndex = button.getAttribute('data-tabs')?.replace('tab-', '') || '0';
63
+ if (buttonTabIndex === tabIndex) {
64
+ button.setAttribute('aria-selected', 'true');
65
+ button.className = this.activeClasses;
66
+ } else {
67
+ button.setAttribute('aria-selected', 'false');
68
+ button.className = this.inactiveClasses;
69
+ }
70
+ });
71
+
72
+ // Mettre à jour les panels
73
+ this.element.querySelectorAll('[data-tabs^="panel-"]').forEach((panel: Element) => {
74
+ const panelIndex = panel.getAttribute('data-tabs')?.replace('panel-', '') || '0';
75
+ if (panelIndex === tabIndex) {
76
+ panel.classList.remove('hidden');
77
+ panel.setAttribute('aria-hidden', 'false');
78
+ } else {
79
+ panel.classList.add('hidden');
80
+ panel.setAttribute('aria-hidden', 'true');
81
+ }
82
+ });
83
+
84
+ // Synchroniser le select
85
+ if (this.select) {
86
+ this.select.value = tabIndex;
87
+ }
88
+ }
89
+
90
+ private onChangeSelect = (event: Event) => {
91
+ this.logger.trace('onChangeSelect', event);
92
+ const tabIndex = this.select?.value;
93
+ const button = this.element.querySelector(`[data-tabs="tab-${tabIndex}"]`) as HTMLElement;
94
+
95
+ if (button) {
96
+ button.click();
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,54 @@
1
+ import {bindable, customAttribute, IEventAggregator, ILogger, INode, resolve} from "aurelia";
2
+ import {Channels, ToasterAction, UiColor, UiToastIcon} from '../enums/event-aggregator';
3
+ import {IToaster} from '../interfaces/event-aggregator';
4
+
5
+ @customAttribute({ name: 'bleet-toaster-trigger', defaultProperty: 'id' })
6
+ export class BleetToasterTriggerCustomAttribute {
7
+
8
+ @bindable id: string = '';
9
+ @bindable() public color: UiColor = UiColor.Info;
10
+ @bindable() public icon: UiToastIcon = UiToastIcon.Info;
11
+ @bindable() public title: string = '';
12
+ @bindable() public content: string = '';
13
+ @bindable() public duration: number = 0; // Duration in milliseconds
14
+ public constructor(
15
+ private readonly logger: ILogger = resolve(ILogger).scopeTo('bleet-toaster-trigger'),
16
+ private readonly element: HTMLElement = resolve(INode) as HTMLElement,
17
+ private readonly ea: IEventAggregator = resolve(IEventAggregator),
18
+ ) {
19
+ this.logger.trace('constructor')
20
+ }
21
+
22
+ public attaching()
23
+ {
24
+ this.logger.trace('attaching');
25
+ }
26
+
27
+ public attached()
28
+ {
29
+ this.logger.trace('attached');
30
+ this.element.addEventListener('click', this.onClick);
31
+ }
32
+
33
+ public detached()
34
+ {
35
+ this.logger.trace('detached');
36
+ this.element.removeEventListener('click', this.onClick);
37
+ }
38
+
39
+ private onClick = (event: Event) => {
40
+ this.logger.trace('onClick', event);
41
+ event.preventDefault();
42
+ this.ea.publish(Channels.Toaster, <IToaster>{
43
+ action: ToasterAction.Add, toast:
44
+ {
45
+ id: this.id,
46
+ duration: this.duration,
47
+ color: this.color,
48
+ icon: this.icon,
49
+ title: this.title,
50
+ content: this.content
51
+ }
52
+ });
53
+ }
54
+ }