derafu-js 0.1.1 → 0.1.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/tabs.js +61 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "derafu-js",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Why use a JS framework if we can do all from scratch?",
5
5
  "main": "src/derafu.js",
6
6
  "files": [
package/src/tabs.js CHANGED
@@ -54,6 +54,9 @@ Tabs.init = function () {
54
54
  });
55
55
  });
56
56
 
57
+ // Inject animation styles for deeplink highlighting.
58
+ Tabs._injectStyles();
59
+
57
60
  // Add magic links, modal button actions, and form elements within tabs.
58
61
  for (const tabPane of document.querySelectorAll('div.tab-pane')) {
59
62
  if (tabPane.id) {
@@ -78,6 +81,58 @@ Tabs.init = function () {
78
81
  }, true);*/
79
82
  };
80
83
 
84
+ /**
85
+ * Injects the CSS keyframe animation for deeplink highlighting once into the
86
+ * document head. Uses Bootstrap CSS variables so colors adapt to any theme.
87
+ *
88
+ * @returns {void}
89
+ */
90
+ Tabs._injectStyles = function () {
91
+ 'use strict';
92
+ if (document.getElementById('derafu-tabs-styles')) return;
93
+ const style = document.createElement('style');
94
+ style.id = 'derafu-tabs-styles';
95
+ style.textContent =
96
+ '@keyframes deeplink-pulse {' +
97
+ '0% { box-shadow: 0 0 0 4px rgba(var(--bs-primary-rgb), 0.5); }' +
98
+ '100% { box-shadow: 0 0 0 0 rgba(var(--bs-primary-rgb), 0); }' +
99
+ '}' +
100
+ '.deeplink-highlight {' +
101
+ 'animation: deeplink-pulse 2s ease-out forwards;' +
102
+ '}';
103
+ document.head.appendChild(style);
104
+ };
105
+
106
+ /**
107
+ * Highlights an element using a pulse animation. For cards the element itself
108
+ * is targeted; for form elements the nearest Bootstrap container is used
109
+ * (.input-group > .form-floating > parentElement) so that prepend/append
110
+ * addons and floating labels are included in the visual feedback.
111
+ * The highlight class is removed automatically when the animation ends.
112
+ *
113
+ * @param {HTMLElement} element - The element to highlight.
114
+ * @param {boolean} isCard - Whether the element is a card.
115
+ * @returns {void}
116
+ */
117
+ Tabs._highlightElement = function (element, isCard) {
118
+ 'use strict';
119
+ const target = isCard
120
+ ? element
121
+ : element.closest('.input-group') ||
122
+ element.closest('.form-floating') ||
123
+ element.parentElement;
124
+ if (!target) return;
125
+ if (isCard) target.classList.add('border-primary');
126
+ target.classList.add('deeplink-highlight');
127
+ target.addEventListener(
128
+ 'animationend',
129
+ function () {
130
+ target.classList.remove('deeplink-highlight', 'border-primary');
131
+ },
132
+ { once: true },
133
+ );
134
+ };
135
+
81
136
  /**
82
137
  * Handles opening tabs and focusing on specific elements based on URL.
83
138
  * @returns {void}
@@ -115,7 +170,7 @@ Tabs.handleTabDeeplink = function (tabElement, elementId) {
115
170
  `${tabElement_id}_${elementId}-card`,
116
171
  );
117
172
  if (cardElement) {
118
- cardElement.classList.add('deeplink', 'border', 'border-danger');
173
+ Tabs._highlightElement(cardElement, true);
119
174
  typeof UI !== 'undefined'
120
175
  ? UI.scroll(cardElement)
121
176
  : cardElement.scrollIntoView({ behavior: 'smooth' });
@@ -133,21 +188,9 @@ Tabs.handleTabDeeplink = function (tabElement, elementId) {
133
188
  }
134
189
  modalInstance.show();
135
190
  }
136
- // If it's a select (including select2), apply border and scroll.
137
- else if (
138
- element.tagName.toLowerCase() === 'select' &&
139
- element.dataset.select2Id
140
- ) {
141
- element.parentElement
142
- .querySelector('span.select2-selection')
143
- .classList.add('deeplink', 'border', 'border-danger');
144
- typeof UI !== 'undefined'
145
- ? UI.scroll(element)
146
- : element.scrollIntoView({ behavior: 'smooth' });
147
- }
148
- // If it's not a modal or select2, apply border and scroll.
191
+ // If it's a form element, highlight its container and scroll.
149
192
  else {
150
- element.classList.add('deeplink', 'border', 'border-danger');
193
+ Tabs._highlightElement(element, false);
151
194
  typeof UI !== 'undefined'
152
195
  ? UI.scroll(element)
153
196
  : element.scrollIntoView({ behavior: 'smooth' });
@@ -258,12 +301,9 @@ Tabs.deeplink = function (deeplink) {
258
301
  history.replaceState(null, null, url);
259
302
  })();
260
303
 
261
- // Clean the deeplink selection class in case they exist.
262
- // This ensures that only one "marked" element exists at the same time.
263
- for (const element of document.querySelectorAll(
264
- '.deeplink.border.border-danger',
265
- )) {
266
- element.classList.remove('deeplink', 'border', 'border-danger');
304
+ // Cancel any in-progress highlight so only one marked element exists at a time.
305
+ for (const element of document.querySelectorAll('.deeplink-highlight')) {
306
+ element.classList.remove('deeplink-highlight', 'border-primary');
267
307
  }
268
308
 
269
309
  // Handle opening tabs based on URL.