bootstrap5-toggle 5.2.0 → 5.3.0-rc1
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/README.md +6 -6
- package/css/bootstrap5-toggle.css +1 -1
- package/css/bootstrap5-toggle.min.css +1 -1
- package/js/bootstrap5-toggle.ecmas.js +381 -45
- package/js/bootstrap5-toggle.ecmas.js.map +1 -1
- package/js/bootstrap5-toggle.ecmas.min.js +2 -2
- package/js/bootstrap5-toggle.ecmas.min.js.map +1 -1
- package/js/bootstrap5-toggle.jquery.js +381 -45
- package/js/bootstrap5-toggle.jquery.js.map +1 -1
- package/js/bootstrap5-toggle.jquery.min.js +2 -2
- package/js/bootstrap5-toggle.jquery.min.js.map +1 -1
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Copyright Notice
|
|
2
|
-
* bootstrap5-toggle v5.
|
|
2
|
+
* bootstrap5-toggle v5.3.0-rc1
|
|
3
3
|
* https://palcarazm.github.io/bootstrap5-toggle/
|
|
4
4
|
* @author 2011-2014 Min Hur (https://github.com/minhur)
|
|
5
5
|
* @author 2018-2019 Brent Ely (https://github.com/gitbrent)
|
|
@@ -66,6 +66,9 @@
|
|
|
66
66
|
this.toggleHandle = this.createToggleHandle();
|
|
67
67
|
this.toggleGroup = this.createToggleGroup();
|
|
68
68
|
this.toggle = document.createElement("div");
|
|
69
|
+
if (options.tooltip) {
|
|
70
|
+
this.tooltipLabels = options.tooltip.title;
|
|
71
|
+
}
|
|
69
72
|
if (this.isVisible()) {
|
|
70
73
|
this.renderToggle(options);
|
|
71
74
|
this.render(state);
|
|
@@ -150,17 +153,23 @@
|
|
|
150
153
|
*/
|
|
151
154
|
DOMBuilder.prototype.renderToggle = function (_a) {
|
|
152
155
|
var _b;
|
|
153
|
-
var style = _a.style, width = _a.width, height = _a.height, tabindex = _a.tabindex;
|
|
156
|
+
var style = _a.style, width = _a.width, height = _a.height, tabindex = _a.tabindex, aria = _a.aria, tooltip = _a.tooltip;
|
|
154
157
|
this.toggle.className = "toggle btn ".concat(this.sizeClass, " ").concat(style);
|
|
155
158
|
this.toggle.dataset.toggle = "toggle";
|
|
156
159
|
this.toggle.tabIndex = tabindex;
|
|
157
|
-
this.toggle.role = "
|
|
160
|
+
this.toggle.role = "switch";
|
|
161
|
+
this.checkbox.tabIndex = -1;
|
|
162
|
+
if (this.invCheckbox)
|
|
163
|
+
this.invCheckbox.tabIndex = -1;
|
|
158
164
|
(_b = this.checkbox.parentElement) === null || _b === void 0 ? void 0 : _b.insertBefore(this.toggle, this.checkbox);
|
|
159
165
|
this.toggle.appendChild(this.checkbox);
|
|
160
166
|
if (this.invCheckbox)
|
|
161
167
|
this.toggle.appendChild(this.invCheckbox);
|
|
162
168
|
this.toggle.appendChild(this.toggleGroup);
|
|
169
|
+
this.handleLabels(aria);
|
|
163
170
|
this.handleToggleSize(width, height);
|
|
171
|
+
if (tooltip)
|
|
172
|
+
this.createTooltip(tooltip);
|
|
164
173
|
this.isBuilt = true;
|
|
165
174
|
};
|
|
166
175
|
/**
|
|
@@ -212,6 +221,24 @@
|
|
|
212
221
|
* @param height The height of the toggle element.
|
|
213
222
|
*/
|
|
214
223
|
DOMBuilder.prototype.handleToggleSize = function (width, height) {
|
|
224
|
+
var _this = this;
|
|
225
|
+
this.cancelPendingAnimationFrame();
|
|
226
|
+
if (typeof requestAnimationFrame === "function") {
|
|
227
|
+
this.requestAnimationFrameId = requestAnimationFrame(function () {
|
|
228
|
+
try {
|
|
229
|
+
_this.calculateToggleSize(width, height);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
console.warn("Error calculating toggle size:", error);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Fallback if requestAnimationFrame is not supported
|
|
238
|
+
this.calculateToggleSize(width, height);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
DOMBuilder.prototype.calculateToggleSize = function (width, height) {
|
|
215
242
|
if (width) {
|
|
216
243
|
this.toggle.style.width = width;
|
|
217
244
|
}
|
|
@@ -253,6 +280,50 @@
|
|
|
253
280
|
var paddingBottom = Number.parseFloat(styles.paddingBottom);
|
|
254
281
|
return (height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom);
|
|
255
282
|
};
|
|
283
|
+
/**
|
|
284
|
+
* Cancels any pending animation frame request if one exists.
|
|
285
|
+
* This is used to prevent unnecessary calculations when the toggle size is being changed.
|
|
286
|
+
*/
|
|
287
|
+
DOMBuilder.prototype.cancelPendingAnimationFrame = function () {
|
|
288
|
+
if (this.requestAnimationFrameId !== undefined && typeof cancelAnimationFrame === "function") {
|
|
289
|
+
cancelAnimationFrame(this.requestAnimationFrameId);
|
|
290
|
+
this.requestAnimationFrameId = undefined;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Handles the aria-labelledby and aria-label attributes of the toggle element.
|
|
295
|
+
* If the checkbox element has a labels property and the length of the labels property is greater than 0,
|
|
296
|
+
* the aria-labelledby attribute of the toggle element is set to the id of the labels elements.
|
|
297
|
+
* Otherwise, the aria-label attribute of the toggle element is set to the label property of the ariaOpts object.
|
|
298
|
+
* @param {AriaToggleOptions} ariaOpts - The object containing the label property to be used for the aria-label attribute.
|
|
299
|
+
*/
|
|
300
|
+
DOMBuilder.prototype.handleLabels = function (ariaOpts) {
|
|
301
|
+
var _a;
|
|
302
|
+
if ((_a = this.checkbox.labels) === null || _a === void 0 ? void 0 : _a.length) {
|
|
303
|
+
var ids = Array.from(this.checkbox.labels)
|
|
304
|
+
.map(function (l) { return l.id; })
|
|
305
|
+
.filter(Boolean);
|
|
306
|
+
if (ids.length) {
|
|
307
|
+
this.toggle.setAttribute("aria-labelledby", ids.join(" "));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
this.toggle.setAttribute("aria-label", ariaOpts.label);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Creates a tooltip for the toggle element.
|
|
316
|
+
* If the tooltip is successfully created, it is stored in the `tooltip` property of the DOMBuilder instance.
|
|
317
|
+
* @param {TooltipOptions} tooltip - The options for the tooltip.
|
|
318
|
+
*/
|
|
319
|
+
DOMBuilder.prototype.createTooltip = function (tooltip) {
|
|
320
|
+
try {
|
|
321
|
+
this.tooltip = new globalThis.window.bootstrap.Tooltip(this.toggle, { placement: tooltip.placement, html: true, title: tooltip.title.on });
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error("Error creating tooltip:", error);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
256
327
|
/**
|
|
257
328
|
* Renders the toggle element based on the provided state if the toggle is already built.
|
|
258
329
|
* This method should be called whenever the state of the toggle changes.
|
|
@@ -265,15 +336,15 @@
|
|
|
265
336
|
this.updateToggleByValue(state);
|
|
266
337
|
this.updateToggleByChecked(state);
|
|
267
338
|
this.updateToggleByState(state);
|
|
339
|
+
this.updateAria(state);
|
|
340
|
+
this.updateTooltip(state);
|
|
268
341
|
};
|
|
269
|
-
/************* ✨ Windsurf Command ⭐ *************/
|
|
270
342
|
/**
|
|
271
343
|
* Updates the class of the toggle element based on the provided state.
|
|
272
344
|
* Removes any existing on/off/indeterminate classes and adds the appropriate class based on the state.
|
|
273
345
|
* If the state is indeterminate, adds the 'indeterminate' class and either the on or off class based on the checked attribute.
|
|
274
346
|
* @param {ToggleState} state The state of the toggle element.
|
|
275
347
|
*/
|
|
276
|
-
/******* 9e620de0-7e60-44a0-b26d-be36099794af *******/
|
|
277
348
|
DOMBuilder.prototype.updateToggleByValue = function (state) {
|
|
278
349
|
this.toggle.classList.remove(this.onStyle, this.offStyle, "off", "indeterminate");
|
|
279
350
|
switch (state.value) {
|
|
@@ -383,6 +454,45 @@
|
|
|
383
454
|
this.invCheckbox.name = this.name;
|
|
384
455
|
}
|
|
385
456
|
};
|
|
457
|
+
/**
|
|
458
|
+
* Updates the aria attributes of the toggle element based on the provided state.
|
|
459
|
+
* Sets aria-checked to "mixed" if the state is indeterminate, otherwise sets it to the string representation of the state's checked attribute.
|
|
460
|
+
* Sets aria-disabled to the string representation of whether the state's status is disabled.
|
|
461
|
+
* Sets aria-readonly to the string representation of whether the state's status is readonly.
|
|
462
|
+
* @param {ToggleState} state The state of the toggle element.
|
|
463
|
+
*/
|
|
464
|
+
DOMBuilder.prototype.updateAria = function (state) {
|
|
465
|
+
if (state.indeterminate) {
|
|
466
|
+
this.toggle.setAttribute("aria-checked", "mixed");
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
this.toggle.setAttribute("aria-checked", String(state.checked));
|
|
470
|
+
}
|
|
471
|
+
this.toggle.setAttribute("aria-disabled", String(state.status === ToggleStateStatus.DISABLED));
|
|
472
|
+
this.toggle.setAttribute("aria-readonly", String(state.status === ToggleStateStatus.READONLY));
|
|
473
|
+
};
|
|
474
|
+
/**
|
|
475
|
+
* Updates the tooltip of the toggle element based on the provided state.
|
|
476
|
+
* Sets the content of the tooltip to the corresponding label based on the state's value.
|
|
477
|
+
* If the tooltip or tooltipLabels are not set, does nothing.
|
|
478
|
+
* @param {ToggleState} state The state of the toggle element.
|
|
479
|
+
*/
|
|
480
|
+
DOMBuilder.prototype.updateTooltip = function (state) {
|
|
481
|
+
if (!this.tooltip || !this.tooltipLabels)
|
|
482
|
+
return;
|
|
483
|
+
switch (state.value) {
|
|
484
|
+
case ToggleStateValue.ON:
|
|
485
|
+
this.tooltip.setContent({ ".tooltip-inner": this.tooltipLabels.on });
|
|
486
|
+
return;
|
|
487
|
+
case ToggleStateValue.OFF:
|
|
488
|
+
this.tooltip.setContent({ ".tooltip-inner": this.tooltipLabels.off });
|
|
489
|
+
return;
|
|
490
|
+
case ToggleStateValue.INDETERMINATE:
|
|
491
|
+
if (this.tooltipLabels.mixed)
|
|
492
|
+
this.tooltip.setContent({ ".tooltip-inner": this.tooltipLabels.mixed });
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
386
496
|
Object.defineProperty(DOMBuilder.prototype, "root", {
|
|
387
497
|
/**
|
|
388
498
|
* Returns the root element of the toggle, which is the container of all toggle elements.
|
|
@@ -401,6 +511,11 @@
|
|
|
401
511
|
*/
|
|
402
512
|
DOMBuilder.prototype.destroy = function () {
|
|
403
513
|
var _a, _b;
|
|
514
|
+
this.cancelPendingAnimationFrame();
|
|
515
|
+
if (this.tooltip) {
|
|
516
|
+
this.tooltip.dispose();
|
|
517
|
+
this.tooltip = undefined;
|
|
518
|
+
}
|
|
404
519
|
(_a = this.toggle.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(this.checkbox, this.toggle);
|
|
405
520
|
this.toggle.remove();
|
|
406
521
|
(_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
@@ -410,9 +525,38 @@
|
|
|
410
525
|
return DOMBuilder;
|
|
411
526
|
}());
|
|
412
527
|
|
|
413
|
-
|
|
528
|
+
var SanitizeMode;
|
|
529
|
+
(function (SanitizeMode) {
|
|
530
|
+
SanitizeMode["HTML"] = "HTML";
|
|
531
|
+
SanitizeMode["TEXT"] = "TEXT";
|
|
532
|
+
})(SanitizeMode || (SanitizeMode = {}));
|
|
533
|
+
/**
|
|
534
|
+
* Sanitizes a given text string according to the provided options.
|
|
535
|
+
* If the input text is null, it will return null.
|
|
536
|
+
* If the input text is not null, it will sanitize the text according to the provided mode.
|
|
537
|
+
* If the mode is HTML, it will sanitize the text using the sanitizeHTML function.
|
|
538
|
+
* If the mode is TEXT, it will sanitize the text using the sanitizeText function.
|
|
539
|
+
* @param text The text string to sanitize.
|
|
540
|
+
* @param opts The options to use for sanitizing the text.
|
|
541
|
+
* @return The sanitized text string, or null if the input text was null.
|
|
542
|
+
*/
|
|
543
|
+
function sanitize(text, opts) {
|
|
414
544
|
if (!text)
|
|
415
545
|
return text;
|
|
546
|
+
switch (opts.mode) {
|
|
547
|
+
case SanitizeMode.HTML:
|
|
548
|
+
return sanitizeHTML(text);
|
|
549
|
+
case SanitizeMode.TEXT:
|
|
550
|
+
return sanitizeText(text);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Sanitizes a given text string, replacing special characters with their HTML entities.
|
|
555
|
+
* If the input text is null, it will return null.
|
|
556
|
+
* @param text The text string to sanitize.
|
|
557
|
+
* @return The sanitized text string, or null if the input text was null.
|
|
558
|
+
*/
|
|
559
|
+
function sanitizeText(text) {
|
|
416
560
|
var map = {
|
|
417
561
|
"&": "&",
|
|
418
562
|
"<": "<",
|
|
@@ -424,6 +568,96 @@
|
|
|
424
568
|
// Using replace with regex for single-pass character mapping compatible with ES5
|
|
425
569
|
return text.replace(/[&<>"'/]/g, function (m) { return map[m]; });
|
|
426
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Sanitizes HTML content using an allow-list approach to prevent XSS attacks.
|
|
573
|
+
*
|
|
574
|
+
* @param html The HTML string to sanitize
|
|
575
|
+
* @param options Configuration options for allowed tags and attributes
|
|
576
|
+
* @returns Sanitized HTML string
|
|
577
|
+
*/
|
|
578
|
+
function sanitizeHTML(html) {
|
|
579
|
+
var config = {
|
|
580
|
+
allowedTags: ["b", "i", "strong", "em", "span", "small", "sup", "sub", "img"],
|
|
581
|
+
allowedAttributes: ["class", "style", "src", "alt", "title", "data-*"]
|
|
582
|
+
};
|
|
583
|
+
// Implementation using DOMParser for browser compatibility
|
|
584
|
+
var parser = new DOMParser();
|
|
585
|
+
var doc = parser.parseFromString(html, "text/html");
|
|
586
|
+
// Sanitize all nodes in the document body
|
|
587
|
+
var bodyChildren = Array.from(doc.body.childNodes);
|
|
588
|
+
bodyChildren.forEach(function (node) { return sanitizeNode(node, config); });
|
|
589
|
+
return doc.body.innerHTML;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Sanitizes a single node in the document tree.
|
|
593
|
+
*
|
|
594
|
+
* For element nodes, it removes disallowed tags and attributes.
|
|
595
|
+
* For text nodes, it keeps them as is.
|
|
596
|
+
*
|
|
597
|
+
* Recursively sanitizes all children of an element node.
|
|
598
|
+
*
|
|
599
|
+
* @param node The node to sanitize
|
|
600
|
+
* @param config Configuration options for allowed tags and attributes
|
|
601
|
+
*/
|
|
602
|
+
function sanitizeNode(node, config) {
|
|
603
|
+
var sanitizeNodeRecursive = function (node) {
|
|
604
|
+
var _a;
|
|
605
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
606
|
+
var element_1 = node;
|
|
607
|
+
var tagName = element_1.tagName.toLowerCase();
|
|
608
|
+
// Remove disallowed tags
|
|
609
|
+
if (!config.allowedTags.includes(tagName)) {
|
|
610
|
+
// Replace disallowed element with its text content
|
|
611
|
+
var fragment_1 = document.createDocumentFragment();
|
|
612
|
+
Array.from(element_1.childNodes).forEach(function (child) {
|
|
613
|
+
fragment_1.appendChild(child.cloneNode(true));
|
|
614
|
+
});
|
|
615
|
+
(_a = element_1.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(fragment_1, element_1);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// Remove disallowed attributes
|
|
619
|
+
Array.from(element_1.attributes).forEach(function (attr) {
|
|
620
|
+
var attrName = attr.name.toLowerCase();
|
|
621
|
+
var isAllowed = config.allowedAttributes.some(function (allowed) {
|
|
622
|
+
return allowed.endsWith("*") ? attrName.startsWith(allowed.slice(0, -1)) : attrName === allowed;
|
|
623
|
+
});
|
|
624
|
+
if (isAllowed) {
|
|
625
|
+
sanitizeAllowedAttr(element_1, attr, attrName);
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
element_1.removeAttribute(attr.name);
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
// Recursively sanitize children
|
|
632
|
+
var children = Array.from(element_1.childNodes);
|
|
633
|
+
children.forEach(sanitizeNodeRecursive);
|
|
634
|
+
}
|
|
635
|
+
else if (node.nodeType === Node.TEXT_NODE) {
|
|
636
|
+
// Text nodes are safe, keep them as is
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
sanitizeNodeRecursive(node);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Sanitizes an allowed attribute by removing it if its value is a dangerous protocol.
|
|
644
|
+
* Only works for "src" and "href" attributes.
|
|
645
|
+
* @param element The element to check for the attribute.
|
|
646
|
+
* @param attr The attribute to check the value of.
|
|
647
|
+
* @param attrName The name of the attribute to check (either "src" or "href").
|
|
648
|
+
*/
|
|
649
|
+
function sanitizeAllowedAttr(element, attr, attrName) {
|
|
650
|
+
if (attrName !== "src" && attrName !== "href")
|
|
651
|
+
return;
|
|
652
|
+
var value = attr.value.toLowerCase();
|
|
653
|
+
// sonar typescript:S1523 - This is security detection, not execution
|
|
654
|
+
var isDangerousProtocol = value.startsWith("javascript:") ||
|
|
655
|
+
value.startsWith("vbscript:") ||
|
|
656
|
+
(value.startsWith("data:") && !value.startsWith("data:image/"));
|
|
657
|
+
if (isDangerousProtocol) {
|
|
658
|
+
element.removeAttribute(attr.name);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
427
661
|
/**
|
|
428
662
|
* Checks if the given string is a valid numeric value.
|
|
429
663
|
*
|
|
@@ -440,6 +674,14 @@
|
|
|
440
674
|
return /^[+-]?\d+(\.\d+)?$/.test(value.toString().trim());
|
|
441
675
|
}
|
|
442
676
|
|
|
677
|
+
var PlacementOptions;
|
|
678
|
+
(function (PlacementOptions) {
|
|
679
|
+
PlacementOptions["TOP"] = "top";
|
|
680
|
+
PlacementOptions["BOTTOM"] = "bottom";
|
|
681
|
+
PlacementOptions["LEFT"] = "left";
|
|
682
|
+
PlacementOptions["RIGHT"] = "right";
|
|
683
|
+
})(PlacementOptions || (PlacementOptions = {}));
|
|
684
|
+
|
|
443
685
|
/**
|
|
444
686
|
* OptionResolver is responsible for reading HTML attributes and user options
|
|
445
687
|
* to build a complete ToggleOptions object.
|
|
@@ -453,13 +695,13 @@
|
|
|
453
695
|
* @param element HTMLInputElement to read
|
|
454
696
|
* @param attrName Attribute name
|
|
455
697
|
* @param options method options
|
|
456
|
-
* @param options.sanitized Flag to indicate if the
|
|
698
|
+
* @param options.sanitized Flag to indicate if the sanitized mode needs to be used (default: `TEXT`)
|
|
457
699
|
* @returns Sanitized attribute value or null
|
|
458
700
|
*/
|
|
459
701
|
OptionResolver.getAttr = function (element, attrName, opts) {
|
|
460
|
-
var _a = (opts !== null && opts !== void 0 ? opts : {}).sanitized, sanitized = _a === void 0 ?
|
|
702
|
+
var _a = (opts !== null && opts !== void 0 ? opts : {}).sanitized, sanitized = _a === void 0 ? SanitizeMode.TEXT : _a;
|
|
461
703
|
var value = element.getAttribute(attrName);
|
|
462
|
-
return
|
|
704
|
+
return sanitize(value, { mode: sanitized });
|
|
463
705
|
};
|
|
464
706
|
/**
|
|
465
707
|
* Returns the value of an attribute, user-provided value, or default value
|
|
@@ -467,25 +709,29 @@
|
|
|
467
709
|
* @param attrName Attribute name
|
|
468
710
|
* @param userValue Value provided by the user
|
|
469
711
|
* @param defaultValue Default value if neither attribute nor user value exists
|
|
470
|
-
* @param sanitized Flag to indicate if the
|
|
712
|
+
* @param sanitized Flag to indicate if the sanitized mode needs to be used (default: `TEXT`)
|
|
471
713
|
* @returns Final attribute value
|
|
472
714
|
*/
|
|
473
715
|
OptionResolver.getAttrOrDefault = function (element, attrName, userValue, defaultValue, sanitized) {
|
|
474
|
-
if (sanitized === void 0) { sanitized =
|
|
475
|
-
|
|
716
|
+
if (sanitized === void 0) { sanitized = SanitizeMode.TEXT; }
|
|
717
|
+
var sanitizedUserValue = typeof userValue === "string" ? sanitize(userValue, { mode: sanitized }) : userValue;
|
|
718
|
+
return OptionResolver.getAttr(element, attrName, { sanitized: sanitized }) ||
|
|
719
|
+
sanitizedUserValue ||
|
|
720
|
+
defaultValue;
|
|
476
721
|
};
|
|
477
722
|
/**
|
|
478
723
|
* Returns the value of an attribute, user-provided value, or marks as deprecated
|
|
479
724
|
* @param element HTMLInputElement to read
|
|
480
725
|
* @param attrName Attribute name
|
|
481
726
|
* @param userValue Value provided by the user
|
|
482
|
-
* @param sanitized Flag to indicate if the
|
|
727
|
+
* @param sanitized Flag to indicate if the sanitized mode needs to be used (default: `TEXT`)
|
|
483
728
|
* @returns Final attribute value or DeprecationConfig.value if not found
|
|
484
729
|
*/
|
|
485
730
|
OptionResolver.getAttrOrDeprecation = function (element, attrName, userValue, sanitized) {
|
|
486
|
-
if (sanitized === void 0) { sanitized =
|
|
731
|
+
if (sanitized === void 0) { sanitized = SanitizeMode.TEXT; }
|
|
732
|
+
var sanitizedUserValue = typeof userValue === "string" ? sanitize(userValue, { mode: sanitized }) : userValue;
|
|
487
733
|
return OptionResolver.getAttr(element, attrName, { sanitized: sanitized }) ||
|
|
488
|
-
|
|
734
|
+
sanitizedUserValue ||
|
|
489
735
|
DeprecationConfig.value;
|
|
490
736
|
};
|
|
491
737
|
/**
|
|
@@ -495,10 +741,11 @@
|
|
|
495
741
|
* @returns Complete ToggleOptions object
|
|
496
742
|
*/
|
|
497
743
|
OptionResolver.resolve = function (element, userOptions) {
|
|
744
|
+
var _a;
|
|
498
745
|
if (userOptions === void 0) { userOptions = {}; }
|
|
499
746
|
var options = {
|
|
500
|
-
onlabel: this.getAttrOrDeprecation(element, "data-onlabel", userOptions.onlabel,
|
|
501
|
-
offlabel: this.getAttrOrDeprecation(element, "data-offlabel", userOptions.offlabel,
|
|
747
|
+
onlabel: this.getAttrOrDeprecation(element, "data-onlabel", userOptions.onlabel, SanitizeMode.HTML),
|
|
748
|
+
offlabel: this.getAttrOrDeprecation(element, "data-offlabel", userOptions.offlabel, SanitizeMode.HTML),
|
|
502
749
|
onstyle: this.getAttrOrDefault(element, "data-onstyle", userOptions.onstyle, OptionResolver.DEFAULT.onstyle),
|
|
503
750
|
offstyle: this.getAttrOrDefault(element, "data-offstyle", userOptions.offstyle, OptionResolver.DEFAULT.offstyle),
|
|
504
751
|
onvalue: this.getAttr(element, "value") || this.getAttrOrDefault(element, "data-onvalue", userOptions.onvalue, OptionResolver.DEFAULT.onvalue),
|
|
@@ -516,6 +763,10 @@
|
|
|
516
763
|
userOptions.tristate ||
|
|
517
764
|
OptionResolver.DEFAULT.tristate,
|
|
518
765
|
name: this.getAttrOrDefault(element, "name", userOptions.name, this.DEFAULT.name),
|
|
766
|
+
aria: {
|
|
767
|
+
label: this.getAttrOrDefault(element, "aria-label", (_a = userOptions.aria) === null || _a === void 0 ? void 0 : _a.label, this.DEFAULT.aria.label),
|
|
768
|
+
},
|
|
769
|
+
tooltip: OptionResolver.resolveTooltipOptions(element, userOptions),
|
|
519
770
|
};
|
|
520
771
|
if (options.width && isNumeric(options.width))
|
|
521
772
|
options.width = "".concat(options.width, "px");
|
|
@@ -524,6 +775,31 @@
|
|
|
524
775
|
DeprecationConfig.handle(options, element, userOptions);
|
|
525
776
|
return options;
|
|
526
777
|
};
|
|
778
|
+
/**
|
|
779
|
+
* Resolve tooltip options from element attributes and user options.
|
|
780
|
+
* @param element HTMLInputElement representing the toggle
|
|
781
|
+
* @param userOptions Options provided by the user
|
|
782
|
+
* @returns Resolved tooltip options or undefined if not found.
|
|
783
|
+
*/
|
|
784
|
+
OptionResolver.resolveTooltipOptions = function (element, userOptions) {
|
|
785
|
+
var _this = this;
|
|
786
|
+
var _a, _b, _c, _d;
|
|
787
|
+
var getTitle = function (attr, userOption) { return _this.getAttrOrDefault(element, attr, userOption, null, SanitizeMode.HTML) || _this.getAttr(element, "data-tooltip-title", { sanitized: SanitizeMode.HTML }); };
|
|
788
|
+
var titleOn = getTitle("data-tooltip-title-on", (_a = userOptions.tooltip) === null || _a === void 0 ? void 0 : _a.title.on);
|
|
789
|
+
var titleOff = getTitle("data-tooltip-title-off", (_b = userOptions.tooltip) === null || _b === void 0 ? void 0 : _b.title.off);
|
|
790
|
+
var titleMixed = getTitle("data-tooltip-title-mixed", (_c = userOptions.tooltip) === null || _c === void 0 ? void 0 : _c.title.mixed);
|
|
791
|
+
if (!titleOn || !titleOff)
|
|
792
|
+
return OptionResolver.DEFAULT.tooltip;
|
|
793
|
+
var placement = this.getAttrOrDefault(element, "data-tooltip-placement", (_d = userOptions.tooltip) === null || _d === void 0 ? void 0 : _d.placement, PlacementOptions.TOP);
|
|
794
|
+
return {
|
|
795
|
+
placement: Object.values(PlacementOptions).includes(placement) ? placement : PlacementOptions.TOP,
|
|
796
|
+
title: {
|
|
797
|
+
on: titleOn,
|
|
798
|
+
off: titleOff,
|
|
799
|
+
mixed: titleMixed !== null && titleMixed !== void 0 ? titleMixed : undefined,
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
};
|
|
527
803
|
/** Default values for all toggle options */
|
|
528
804
|
OptionResolver.DEFAULT = {
|
|
529
805
|
onlabel: "On",
|
|
@@ -541,6 +817,8 @@
|
|
|
541
817
|
tabindex: 0,
|
|
542
818
|
tristate: false,
|
|
543
819
|
name: null,
|
|
820
|
+
aria: { label: "Toggle", },
|
|
821
|
+
tooltip: undefined,
|
|
544
822
|
};
|
|
545
823
|
return OptionResolver;
|
|
546
824
|
}());
|
|
@@ -565,9 +843,9 @@
|
|
|
565
843
|
DeprecationConfig.handle = function (options, element, userOptions) {
|
|
566
844
|
var _this = this;
|
|
567
845
|
this.deprecatedOptions.forEach(function (_a) {
|
|
568
|
-
var currentOpt = _a.currentOpt, deprecatedAttr = _a.deprecatedAttr, deprecatedOpt = _a.deprecatedOpt;
|
|
846
|
+
var currentOpt = _a.currentOpt, deprecatedAttr = _a.deprecatedAttr, deprecatedOpt = _a.deprecatedOpt, mode = _a.mode;
|
|
569
847
|
if (options[currentOpt] === DeprecationConfig.value) {
|
|
570
|
-
var deprecatedAttrSanitized = sanitize(element.getAttribute(deprecatedAttr));
|
|
848
|
+
var deprecatedAttrSanitized = sanitize(element.getAttribute(deprecatedAttr), { mode: mode });
|
|
571
849
|
if (deprecatedAttrSanitized) {
|
|
572
850
|
_this.log(OptionType.ATTRIBUTE, deprecatedAttr, "data-".concat(currentOpt));
|
|
573
851
|
options[currentOpt] = deprecatedAttrSanitized;
|
|
@@ -599,11 +877,13 @@
|
|
|
599
877
|
currentOpt: "onlabel",
|
|
600
878
|
deprecatedAttr: "data-on",
|
|
601
879
|
deprecatedOpt: "on",
|
|
880
|
+
mode: SanitizeMode.HTML
|
|
602
881
|
},
|
|
603
882
|
{
|
|
604
883
|
currentOpt: "offlabel",
|
|
605
884
|
deprecatedAttr: "data-off",
|
|
606
885
|
deprecatedOpt: "off",
|
|
886
|
+
mode: SanitizeMode.HTML
|
|
607
887
|
},
|
|
608
888
|
];
|
|
609
889
|
return DeprecationConfig;
|
|
@@ -814,6 +1094,17 @@
|
|
|
814
1094
|
return StateReducer;
|
|
815
1095
|
}());
|
|
816
1096
|
|
|
1097
|
+
var ToggleEvents;
|
|
1098
|
+
(function (ToggleEvents) {
|
|
1099
|
+
ToggleEvents["ON"] = "toggle:on";
|
|
1100
|
+
ToggleEvents["OFF"] = "toggle:off";
|
|
1101
|
+
ToggleEvents["MIXED"] = "toggle:mixed";
|
|
1102
|
+
ToggleEvents["ENABLED"] = "toggle:enabled";
|
|
1103
|
+
ToggleEvents["DISABLED"] = "toggle:disabled";
|
|
1104
|
+
ToggleEvents["READONLY"] = "toggle:readonly";
|
|
1105
|
+
})(ToggleEvents || (ToggleEvents = {}));
|
|
1106
|
+
var ToggleEvents$1 = ToggleEvents;
|
|
1107
|
+
|
|
817
1108
|
var Toggle = /** @class */ (function () {
|
|
818
1109
|
/**
|
|
819
1110
|
* Initializes a new instance of the BootstrapToggle class.
|
|
@@ -834,7 +1125,7 @@
|
|
|
834
1125
|
* of the toggle changes its state and triggering the update method to keep the toggle in sync.
|
|
835
1126
|
*/
|
|
836
1127
|
this.onExternalChange = function () {
|
|
837
|
-
_this.update(
|
|
1128
|
+
_this.update();
|
|
838
1129
|
};
|
|
839
1130
|
this.onFormReset = function () {
|
|
840
1131
|
setTimeout(function () { return _this.onExternalChange(); }, 0);
|
|
@@ -912,7 +1203,7 @@
|
|
|
912
1203
|
_this.domBuilder.root.removeEventListener("pointercancel", _this.onPointerCancel);
|
|
913
1204
|
};
|
|
914
1205
|
this.handlerKeyboardEvent = function (e) {
|
|
915
|
-
if (e.key
|
|
1206
|
+
if (e.key === " " || e.key === "Enter") {
|
|
916
1207
|
_this.apply(ToggleActionType.NEXT);
|
|
917
1208
|
}
|
|
918
1209
|
};
|
|
@@ -1050,24 +1341,24 @@
|
|
|
1050
1341
|
this.domBuilder.root.removeEventListener("pointerdown", this.onPointerDown);
|
|
1051
1342
|
};
|
|
1052
1343
|
/**
|
|
1053
|
-
* Binds a
|
|
1054
|
-
* The event listener is responsible for handling
|
|
1055
|
-
* and triggering the toggle's state change when a
|
|
1344
|
+
* Binds a keydown event listener to the root element of the toggle.
|
|
1345
|
+
* The event listener is responsible for handling keydown events
|
|
1346
|
+
* and triggering the toggle's state change when a keydown event occurs.
|
|
1056
1347
|
* The event listener is bound with the passive option, which means that it will not block
|
|
1057
1348
|
* other event listeners from being triggered.
|
|
1058
1349
|
*/
|
|
1059
1350
|
Toggle.prototype.bindKeyboardEventListener = function () {
|
|
1060
|
-
this.domBuilder.root.addEventListener("
|
|
1351
|
+
this.domBuilder.root.addEventListener("keydown", this.handlerKeyboardEvent, { passive: true });
|
|
1061
1352
|
};
|
|
1062
1353
|
/**
|
|
1063
|
-
* Unbinds the
|
|
1064
|
-
* This method is responsible for unbinding the
|
|
1354
|
+
* Unbinds the keydown event listener from the root element of the toggle.
|
|
1355
|
+
* This method is responsible for unbinding the keydown event listener that was
|
|
1065
1356
|
* previously bound by the bindKeyboardEventListener method.
|
|
1066
1357
|
* If the event listener is not bound (i.e. this.eventsBound is false), this method does nothing.
|
|
1067
1358
|
* @returns void
|
|
1068
1359
|
*/
|
|
1069
1360
|
Toggle.prototype.unbindKeyboardEventListener = function () {
|
|
1070
|
-
this.domBuilder.root.removeEventListener("
|
|
1361
|
+
this.domBuilder.root.removeEventListener("keydown", this.handlerKeyboardEvent);
|
|
1071
1362
|
};
|
|
1072
1363
|
/**
|
|
1073
1364
|
* Binds a click event listener to all labels that are associated with the toggle's input element.
|
|
@@ -1118,9 +1409,10 @@
|
|
|
1118
1409
|
return;
|
|
1119
1410
|
this.suppressExternalSync = true;
|
|
1120
1411
|
try {
|
|
1121
|
-
this.
|
|
1412
|
+
var state = this.stateReducer.get();
|
|
1413
|
+
this.domBuilder.render(state);
|
|
1122
1414
|
if (!silent)
|
|
1123
|
-
this.trigger();
|
|
1415
|
+
this.trigger(action, state);
|
|
1124
1416
|
}
|
|
1125
1417
|
finally {
|
|
1126
1418
|
this.suppressExternalSync = false;
|
|
@@ -1201,31 +1493,75 @@
|
|
|
1201
1493
|
this.apply(ToggleActionType.READONLY);
|
|
1202
1494
|
};
|
|
1203
1495
|
/**
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
*/
|
|
1208
|
-
Toggle.prototype.update = function (silent) {
|
|
1496
|
+
* Synchronizes the toggle state with the input element and renders the toggle.
|
|
1497
|
+
*/
|
|
1498
|
+
Toggle.prototype.update = function () {
|
|
1209
1499
|
this.suppressExternalSync = true;
|
|
1210
1500
|
try {
|
|
1211
1501
|
this.stateReducer.sync(this.element);
|
|
1212
1502
|
this.domBuilder.render(this.stateReducer.get());
|
|
1213
|
-
if (!silent)
|
|
1214
|
-
this.trigger();
|
|
1215
1503
|
}
|
|
1216
1504
|
finally {
|
|
1217
1505
|
this.suppressExternalSync = false;
|
|
1218
1506
|
}
|
|
1219
1507
|
};
|
|
1220
1508
|
/**
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1509
|
+
* Triggers the change event on the toggle's input element and the appropriate toggle event.
|
|
1510
|
+
* This method is called after a toggle action is applied to notify listeners of the state change.
|
|
1511
|
+
* @param {ToggleActionType} action The toggle action that was applied.
|
|
1512
|
+
* @param {ToggleState} state The state of the toggle once the action was applied.
|
|
1513
|
+
*/
|
|
1514
|
+
Toggle.prototype.trigger = function (action, state) {
|
|
1515
|
+
this.element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1516
|
+
var eventName = this.getEventForAction(action, state);
|
|
1517
|
+
var detail = { state: state };
|
|
1518
|
+
this.element.dispatchEvent(new CustomEvent(eventName, {
|
|
1519
|
+
bubbles: true,
|
|
1520
|
+
detail: detail
|
|
1521
|
+
}));
|
|
1522
|
+
};
|
|
1523
|
+
/**
|
|
1524
|
+
* Returns the corresponding toggle event for the given toggle action and state.
|
|
1525
|
+
* This method is used to determine which toggle event to trigger after a toggle action is applied.
|
|
1526
|
+
* @param {ToggleActionType} action The toggle action that was applied.
|
|
1527
|
+
* @param {ToggleState} state The previous state of the toggle before the action was applied.
|
|
1528
|
+
* @returns {ToggleEvents} The corresponding toggle event for the given toggle action and state.
|
|
1529
|
+
*/
|
|
1530
|
+
Toggle.prototype.getEventForAction = function (action, state) {
|
|
1531
|
+
switch (action) {
|
|
1532
|
+
case ToggleActionType.ON:
|
|
1533
|
+
return ToggleEvents$1.ON;
|
|
1534
|
+
case ToggleActionType.OFF:
|
|
1535
|
+
return ToggleEvents$1.OFF;
|
|
1536
|
+
case ToggleActionType.INDETERMINATE:
|
|
1537
|
+
return ToggleEvents$1.MIXED;
|
|
1538
|
+
case ToggleActionType.ENABLE:
|
|
1539
|
+
return ToggleEvents$1.ENABLED;
|
|
1540
|
+
case ToggleActionType.DISABLE:
|
|
1541
|
+
return ToggleEvents$1.DISABLED;
|
|
1542
|
+
case ToggleActionType.READONLY:
|
|
1543
|
+
return ToggleEvents$1.READONLY;
|
|
1544
|
+
case ToggleActionType.DETERMINATE:
|
|
1545
|
+
case ToggleActionType.TOGGLE:
|
|
1546
|
+
case ToggleActionType.NEXT:
|
|
1547
|
+
return this.getValueEvent(state);
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
/**
|
|
1551
|
+
* Returns the corresponding toggle event for the given toggle state.
|
|
1552
|
+
* This method is used to determine which toggle event to trigger after a toggle action is applied.
|
|
1553
|
+
* @param {ToggleState} state The previous state of the toggle before the action was applied.
|
|
1554
|
+
* @returns {ToggleEvents} The corresponding toggle event for the given toggle state.
|
|
1555
|
+
*/
|
|
1556
|
+
Toggle.prototype.getValueEvent = function (state) {
|
|
1557
|
+
switch (state.value) {
|
|
1558
|
+
case ToggleStateValue.ON:
|
|
1559
|
+
return ToggleEvents$1.ON;
|
|
1560
|
+
case ToggleStateValue.OFF:
|
|
1561
|
+
return ToggleEvents$1.OFF;
|
|
1562
|
+
case ToggleStateValue.INDETERMINATE:
|
|
1563
|
+
return ToggleEvents$1.MIXED;
|
|
1564
|
+
}
|
|
1229
1565
|
};
|
|
1230
1566
|
/**
|
|
1231
1567
|
* Destroys the toggle element and unbinds all event listeners.
|