derafu-js 0.1.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/src/tabs.js ADDED
@@ -0,0 +1,267 @@
1
+ /*! Tabs functions | (c) 2025 Derafu DEV | MIT */
2
+
3
+ // Object where tabs functions will be assigned.
4
+ const Tabs = {};
5
+
6
+ /**
7
+ * [DEPRECATED] Initializes tabs and manages navigation and display based on URL.
8
+ * Opens the first tab by default and the tab specified in the URL, if it exists.
9
+ * Adds magic links and updates the URL when changing tabs.
10
+ *
11
+ * @deprecated This function is deprecated and will be removed in future versions.
12
+ * Use Tabs.init() instead.
13
+ * @returns {*} Returns the result of Tabs.init().
14
+ */
15
+ Tabs.init_old = function () {
16
+ 'use strict';
17
+ console.warn('Tabs.init_old() is deprecated. Use Tabs.init() instead.');
18
+ return Tabs.init();
19
+ };
20
+
21
+ /**
22
+ * Initializes tabs and manages navigation and display based on URL.
23
+ * Opens the first tab by default and the tab specified in the URL, if it exists.
24
+ * Adds magic links and updates the URL when changing tabs.
25
+ *
26
+ * @returns {void}
27
+ */
28
+ Tabs.init = function () {
29
+ 'use strict';
30
+
31
+ // Open the first tab by default.
32
+ const firstTabLink = document.querySelector('.nav-tabs a');
33
+ if (firstTabLink) {
34
+ new bootstrap.Tab(firstTabLink).show();
35
+ }
36
+
37
+ // Handle opening tabs based on URL.
38
+ Tabs.handleURLDeeplink();
39
+
40
+ // Update URL when clicking on each tab.
41
+ const tabLinks = document.querySelectorAll('a[data-bs-toggle="tab"]');
42
+ tabLinks.forEach(tabLink => {
43
+ tabLink.addEventListener('click', function () {
44
+ Tabs.deeplink(tabLink.getAttribute('aria-controls'));
45
+ });
46
+ });
47
+
48
+ // Add magic links, modal button actions, and form elements within tabs.
49
+ for (const tabPane of document.querySelectorAll('div.tab-pane')) {
50
+ if (tabPane.id) {
51
+ Tabs.addLinksToTabCards(tabPane);
52
+ Tabs.bindModalButtons(tabPane);
53
+ Tabs.bindFormElements(tabPane);
54
+ }
55
+ }
56
+
57
+ // Check if there are open modals after closing the current one.
58
+ // If there are no more open modals, manually remove the backdrop.
59
+ // This is useful if you have some process that opens multiple modals
60
+ // and modal-backdrop elements are left by error without being removed by
61
+ // Bootstrap.
62
+ /*document.addEventListener('hidden.bs.modal', function(event) {
63
+ if (!document.querySelector('.modal.show')) {
64
+ const backdrops = document.querySelectorAll('.modal-backdrop');
65
+ backdrops.forEach((backdrop) => {
66
+ backdrop.remove();
67
+ });
68
+ }
69
+ }, true);*/
70
+ };
71
+
72
+ /**
73
+ * Handles opening tabs and focusing on specific elements based on URL.
74
+ * @returns {void}
75
+ */
76
+ Tabs.handleURLDeeplink = function () {
77
+ 'use strict';
78
+ const url = document.location.toString();
79
+ if (url.match('#')) {
80
+ const deeplink = url.split('#')[1];
81
+ if (deeplink !== '') {
82
+ const [tabId, elementId] = deeplink.split(':');
83
+ const tabElement = document.getElementById(`${tabId}-tab`);
84
+ if (tabElement) {
85
+ new bootstrap.Tab(tabElement).show();
86
+ Tabs.handleTabDeeplink(tabElement, elementId);
87
+ }
88
+ }
89
+ }
90
+ };
91
+
92
+ /**
93
+ * Focuses on a specific element within a tab.
94
+ *
95
+ * @param {HTMLElement} tabElement - Tab element that has been activated.
96
+ * @param {string} elementId - ID of the specific element within the tab to
97
+ * focus on.
98
+ * @returns {void}
99
+ */
100
+ Tabs.handleTabDeeplink = function (tabElement, elementId) {
101
+ 'use strict';
102
+
103
+ // If the elementId is part of a card ID, scroll to the card.
104
+ const tabElement_id = tabElement.id.replace('-tab', '');
105
+ const cardElement = document.getElementById(
106
+ `${tabElement_id}_${elementId}-card`,
107
+ );
108
+ if (cardElement) {
109
+ cardElement.classList.add('deeplink', 'border', 'border-danger');
110
+ typeof UI !== 'undefined'
111
+ ? UI.scroll(cardElement)
112
+ : cardElement.scrollIntoView({ behavior: 'smooth' });
113
+ return;
114
+ }
115
+
116
+ // If the elementId exists on its own, handle according to its type.
117
+ const element = document.getElementById(elementId);
118
+ if (element) {
119
+ // If it's a modal, open it.
120
+ if (element.classList.contains('modal')) {
121
+ let modalInstance = bootstrap.Modal.getInstance(element);
122
+ if (!modalInstance) {
123
+ modalInstance = new bootstrap.Modal(element);
124
+ }
125
+ modalInstance.show();
126
+ }
127
+ // If it's a select (including select2), apply border and scroll.
128
+ else if (
129
+ element.tagName.toLowerCase() === 'select' &&
130
+ element.dataset.select2Id
131
+ ) {
132
+ element.parentElement
133
+ .querySelector('span.select2-selection')
134
+ .classList.add('deeplink', 'border', 'border-danger');
135
+ typeof UI !== 'undefined'
136
+ ? UI.scroll(element)
137
+ : element.scrollIntoView({ behavior: 'smooth' });
138
+ }
139
+ // If it's not a modal or select2, apply border and scroll.
140
+ else {
141
+ element.classList.add('deeplink', 'border', 'border-danger');
142
+ typeof UI !== 'undefined'
143
+ ? UI.scroll(element)
144
+ : element.scrollIntoView({ behavior: 'smooth' });
145
+ }
146
+ }
147
+ // If it's not a card or specific ID, scroll to the tab.
148
+ else {
149
+ typeof UI !== 'undefined'
150
+ ? UI.scroll(tabElement)
151
+ : tabElement.scrollIntoView({ behavior: 'smooth' });
152
+ }
153
+ };
154
+
155
+ /**
156
+ * Adds magic links to cards within each tab pane.
157
+ *
158
+ * @param {HTMLElement} tabPane - The tab panel where links will be added.
159
+ * @returns {void}
160
+ */
161
+ Tabs.addLinksToTabCards = function (tabPane) {
162
+ 'use strict';
163
+ for (const card of tabPane.querySelectorAll('div.card')) {
164
+ const cardId = card.id;
165
+ if (
166
+ cardId &&
167
+ cardId.startsWith(`${tabPane.id}_`) &&
168
+ cardId.endsWith('-card')
169
+ ) {
170
+ const header = card.querySelector('div.card-header');
171
+ if (header) {
172
+ const link = document.createElement('a');
173
+ const deeplink = `${tabPane.id}:${cardId.replace(`${tabPane.id}_`, '').replace('-card', '')}`;
174
+ link.href = `#${deeplink}`;
175
+ link.className = 'float-end';
176
+ link.innerHTML = `<i class="fa-solid fa-link fa-fw text-muted" onclick="Tabs.deeplink('${deeplink}'); return false"></i>`;
177
+ header.insertBefore(link, header.firstChild);
178
+ }
179
+ }
180
+ }
181
+ };
182
+
183
+ /**
184
+ * Binds modal actions to buttons within each tab pane.
185
+ *
186
+ * @param {HTMLElement} tabPane - The tab panel where buttons will be bound.
187
+ * @returns {void}
188
+ */
189
+ Tabs.bindModalButtons = function (tabPane) {
190
+ 'use strict';
191
+ for (const button of tabPane.querySelectorAll(
192
+ 'button[data-bs-toggle="modal"]',
193
+ )) {
194
+ button.setAttribute(
195
+ 'onclick',
196
+ `Tabs.deeplink('${tabPane.id}:${button.dataset.bsTarget.replace('#', '')}')`,
197
+ );
198
+ }
199
+ };
200
+
201
+ /**
202
+ * Binds click actions to form element labels within each tab pane.
203
+ *
204
+ * @param {HTMLElement} tabPane - The tab panel where buttons will be bound.
205
+ * @returns {void}
206
+ */
207
+ Tabs.bindFormElements = function (tabPane) {
208
+ 'use strict';
209
+ for (const formElement of tabPane.querySelectorAll(
210
+ 'form input, form select, form textarea',
211
+ )) {
212
+ if (formElement.id) {
213
+ const label = tabPane.querySelector(
214
+ `label[for="${formElement.id}"]`,
215
+ );
216
+ if (label) {
217
+ label.setAttribute(
218
+ 'onclick',
219
+ `Tabs.deeplink('${tabPane.id}:${formElement.id}')`,
220
+ );
221
+ }
222
+ }
223
+ }
224
+ };
225
+
226
+ /**
227
+ * Updates the browser URL and handles the display of tabs and elements based on
228
+ * the provided deeplink. This function is responsible for updating the URL
229
+ * without reloading the page, cleaning previously marked elements with borders
230
+ * (if any), and calling the handleURLDeeplink function to handle the specific
231
+ * URL logic.
232
+ *
233
+ * @param {string} deeplink - The deeplink that represents the tab and/or the
234
+ * specific element to focus on. The expected format is 'tabId' or
235
+ * 'tabId:elementId'.
236
+ * @returns {void}
237
+ */
238
+ Tabs.deeplink = function (deeplink) {
239
+ // Assign deeplink to URL.
240
+ typeof UI !== 'undefined'
241
+ ? UI.deeplink(deeplink)
242
+ : (() => {
243
+ const _base =
244
+ typeof window._base !== 'undefined'
245
+ ? window._base
246
+ : location.href.split('#')[0];
247
+ const _request = window._request || '';
248
+ const url = `${_base}${_request}#${deeplink}`;
249
+ history.replaceState(null, null, url);
250
+ })();
251
+
252
+ // Clean the deeplink selection class in case they exist.
253
+ // This ensures that only one "marked" element exists at the same time.
254
+ for (const element of document.querySelectorAll(
255
+ '.deeplink.border.border-danger',
256
+ )) {
257
+ element.classList.remove('deeplink', 'border', 'border-danger');
258
+ }
259
+
260
+ // Handle opening tabs based on URL.
261
+ Tabs.handleURLDeeplink();
262
+ };
263
+
264
+ // Export module for use in Node.js.
265
+ if (typeof module === 'object' && module.exports) {
266
+ module.exports = Tabs;
267
+ }