derafu-js 0.1.1 → 0.1.3
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/package.json +1 -1
- package/src/form/fields.js +46 -0
- package/src/tabs.js +61 -21
package/package.json
CHANGED
package/src/form/fields.js
CHANGED
|
@@ -84,6 +84,52 @@ FormFields.showPassword = function (button) {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Fills the password field next to the button with a randomly generated
|
|
89
|
+
* password.
|
|
90
|
+
*
|
|
91
|
+
* Relies on Utils.generatePassword() to create the value, so utils.js must
|
|
92
|
+
* be loaded before this file for this function to work.
|
|
93
|
+
*
|
|
94
|
+
* @param {HTMLElement} button - Button used to activate the function, located
|
|
95
|
+
* next to the password field.
|
|
96
|
+
* @param {number} [length] - Length of the generated password. Default is 12
|
|
97
|
+
* (see Utils.generatePassword()).
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
100
|
+
FormFields.generatePassword = function (button, length) {
|
|
101
|
+
'use strict';
|
|
102
|
+
if (!(button instanceof HTMLElement)) {
|
|
103
|
+
console.error(
|
|
104
|
+
'FormFields.generatePassword: The provided argument is not a valid HTML element.',
|
|
105
|
+
);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
typeof Utils === 'undefined' ||
|
|
110
|
+
typeof Utils.generatePassword !== 'function'
|
|
111
|
+
) {
|
|
112
|
+
console.error(
|
|
113
|
+
'FormFields.generatePassword: Utils.generatePassword() is not available. Make sure utils.js is loaded before this file.',
|
|
114
|
+
);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Try to find the related password field.
|
|
119
|
+
const input = button
|
|
120
|
+
.closest('.input-group')
|
|
121
|
+
.querySelector('input[type="password"], input[type="text"]');
|
|
122
|
+
|
|
123
|
+
if (!input) {
|
|
124
|
+
console.error('FormFields.generatePassword: Input not found.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fill the field and notify listeners (e.g. validation) of the change.
|
|
129
|
+
input.value = Utils.generatePassword(length);
|
|
130
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
131
|
+
};
|
|
132
|
+
|
|
87
133
|
/**
|
|
88
134
|
* Allows editing the content of a text field in a larger dialog box.
|
|
89
135
|
* Useful for text fields with extensive content that would benefit from an
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
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.
|