bootstrap5-toggle 5.2.0 → 5.3.0-rc2

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.
@@ -1,5 +1,5 @@
1
1
  /* Copyright Notice
2
- * bootstrap5-toggle v5.2.0
2
+ * bootstrap5-toggle v5.3.0-rc2
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)
@@ -19,7 +19,7 @@
19
19
  (function (ToggleStateValue) {
20
20
  ToggleStateValue["ON"] = "on";
21
21
  ToggleStateValue["OFF"] = "off";
22
- ToggleStateValue["INDETERMINATE"] = "indeterminate";
22
+ ToggleStateValue["MIXED"] = "mixed";
23
23
  })(ToggleStateValue || (ToggleStateValue = {}));
24
24
  var ToggleStateStatus;
25
25
  (function (ToggleStateStatus) {
@@ -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 = "button";
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) {
@@ -283,7 +354,7 @@
283
354
  case ToggleStateValue.OFF:
284
355
  this.toggle.classList.add(this.offStyle, "off");
285
356
  break;
286
- case ToggleStateValue.INDETERMINATE:
357
+ case ToggleStateValue.MIXED:
287
358
  this.toggle.classList.add("indeterminate");
288
359
  if (state.checked) {
289
360
  this.toggle.classList.add(this.onStyle);
@@ -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.MIXED:
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
- function sanitize(text) {
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
  "<": "&lt;",
@@ -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 attribute value needs to be sanitized (default: `true`)
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 ? true : _a;
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 sanitized ? sanitize(value) : value;
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 attribute value needs to be sanitized (default: {@code true})
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 = true; }
475
- return OptionResolver.getAttr(element, attrName, { sanitized: sanitized }) || userValue || defaultValue;
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 attribute value needs to be sanitized (default: {@code true})
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 = true; }
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
- userValue ||
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, false),
501
- offlabel: this.getAttrOrDeprecation(element, "data-offlabel", userOptions.offlabel, false),
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;
@@ -655,7 +935,7 @@
655
935
  var indeterminate = this.isTristate && element.indeterminate;
656
936
  var value;
657
937
  if (indeterminate) {
658
- value = ToggleStateValue.INDETERMINATE;
938
+ value = ToggleStateValue.MIXED;
659
939
  }
660
940
  else if (checked) {
661
941
  value = ToggleStateValue.ON;
@@ -729,9 +1009,9 @@
729
1009
  return this.do(ToggleActionType.ON);
730
1010
  return false;
731
1011
  case ToggleActionType.INDETERMINATE:
732
- return this.setValueIfChanged(ToggleStateValue.INDETERMINATE, undefined, true);
1012
+ return this.setValueIfChanged(ToggleStateValue.MIXED, undefined, true);
733
1013
  case ToggleActionType.DETERMINATE:
734
- if (this.state.value != ToggleStateValue.INDETERMINATE)
1014
+ if (this.state.value != ToggleStateValue.MIXED)
735
1015
  return false;
736
1016
  return this.setValue(this.state.checked ? ToggleStateValue.ON : ToggleStateValue.OFF, this.state.checked, false);
737
1017
  case ToggleActionType.NEXT:
@@ -798,7 +1078,7 @@
798
1078
  if (this.state.value === ToggleStateValue.ON || this.state.value === ToggleStateValue.OFF) {
799
1079
  return this.do(ToggleActionType.INDETERMINATE);
800
1080
  }
801
- if (this.state.value === ToggleStateValue.INDETERMINATE) {
1081
+ if (this.state.value === ToggleStateValue.MIXED) {
802
1082
  return this.state.checked
803
1083
  ? this.do(ToggleActionType.OFF)
804
1084
  : this.do(ToggleActionType.ON);
@@ -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(true);
1128
+ _this.update();
838
1129
  };
839
1130
  this.onFormReset = function () {
840
1131
  setTimeout(function () { return _this.onExternalChange(); }, 0);
@@ -912,7 +1203,8 @@
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") {
1207
+ e.preventDefault();
916
1208
  _this.apply(ToggleActionType.NEXT);
917
1209
  }
918
1210
  };
@@ -1050,24 +1342,22 @@
1050
1342
  this.domBuilder.root.removeEventListener("pointerdown", this.onPointerDown);
1051
1343
  };
1052
1344
  /**
1053
- * Binds a keypress event listener to the root element of the toggle.
1054
- * The event listener is responsible for handling keypress events
1055
- * and triggering the toggle's state change when a keypress event occurs.
1056
- * The event listener is bound with the passive option, which means that it will not block
1057
- * other event listeners from being triggered.
1345
+ * Binds a keydown event listener to the root element of the toggle.
1346
+ * The event listener is responsible for handling keydown events
1347
+ * and triggering the toggle's state change when a keydown event occurs.
1058
1348
  */
1059
1349
  Toggle.prototype.bindKeyboardEventListener = function () {
1060
- this.domBuilder.root.addEventListener("keypress", this.handlerKeyboardEvent, { passive: true });
1350
+ this.domBuilder.root.addEventListener("keydown", this.handlerKeyboardEvent, { passive: false });
1061
1351
  };
1062
1352
  /**
1063
- * Unbinds the keypress event listener from the root element of the toggle.
1064
- * This method is responsible for unbinding the keypress event listener that was
1353
+ * Unbinds the keydown event listener from the root element of the toggle.
1354
+ * This method is responsible for unbinding the keydown event listener that was
1065
1355
  * previously bound by the bindKeyboardEventListener method.
1066
1356
  * If the event listener is not bound (i.e. this.eventsBound is false), this method does nothing.
1067
1357
  * @returns void
1068
1358
  */
1069
1359
  Toggle.prototype.unbindKeyboardEventListener = function () {
1070
- this.domBuilder.root.removeEventListener("keypress", this.handlerKeyboardEvent);
1360
+ this.domBuilder.root.removeEventListener("keydown", this.handlerKeyboardEvent);
1071
1361
  };
1072
1362
  /**
1073
1363
  * Binds a click event listener to all labels that are associated with the toggle's input element.
@@ -1118,9 +1408,10 @@
1118
1408
  return;
1119
1409
  this.suppressExternalSync = true;
1120
1410
  try {
1121
- this.domBuilder.render(this.stateReducer.get());
1411
+ var state = this.stateReducer.get();
1412
+ this.domBuilder.render(state);
1122
1413
  if (!silent)
1123
- this.trigger();
1414
+ this.trigger(action, state);
1124
1415
  }
1125
1416
  finally {
1126
1417
  this.suppressExternalSync = false;
@@ -1178,54 +1469,104 @@
1178
1469
  * Enables the toggle.
1179
1470
  * If the toggle is currently disabled, this method will set the toggle state to enabled.
1180
1471
  * If the silent parameter is false, this method will also trigger the change event.
1472
+ * @param {boolean} silent A boolean indicating whether to trigger the change event after applying the action.
1181
1473
  * @returns void
1182
1474
  */
1183
- Toggle.prototype.enable = function () {
1184
- this.apply(ToggleActionType.ENABLE);
1475
+ Toggle.prototype.enable = function (silent) {
1476
+ if (silent === void 0) { silent = false; }
1477
+ this.apply(ToggleActionType.ENABLE, silent);
1185
1478
  };
1186
1479
  /**
1187
1480
  * Disables the toggle.
1188
1481
  * If the toggle is currently enabled, this method will set the toggle state to disabled.
1189
1482
  * If the silent parameter is false, this method will also trigger the change event.
1483
+ * @param {boolean} silent A boolean indicating whether to trigger the change event after applying the action.
1190
1484
  */
1191
- Toggle.prototype.disable = function () {
1192
- this.apply(ToggleActionType.DISABLE);
1485
+ Toggle.prototype.disable = function (silent) {
1486
+ if (silent === void 0) { silent = false; }
1487
+ this.apply(ToggleActionType.DISABLE, silent);
1193
1488
  };
1194
1489
  /**
1195
1490
  * Sets the toggle state to readonly.
1196
1491
  * If the toggle is currently disabled or enabled, this method will set the toggle state to readonly.
1197
1492
  * If the silent parameter is false, this method will also trigger the change event.
1493
+ * @param {boolean} silent A boolean indicating whether to trigger the change event after applying the action.
1198
1494
  * @returns void
1199
1495
  */
1200
- Toggle.prototype.readonly = function () {
1201
- this.apply(ToggleActionType.READONLY);
1496
+ Toggle.prototype.readonly = function (silent) {
1497
+ if (silent === void 0) { silent = false; }
1498
+ this.apply(ToggleActionType.READONLY, silent);
1202
1499
  };
1203
1500
  /**
1204
- * Synchronizes the toggle state with the input element and renders the toggle.
1205
- * If the silent parameter is false, this method will also trigger the change event.
1206
- * @param {boolean} silent A boolean indicating whether to trigger the change event after synchronizing the toggle state.
1207
- */
1208
- Toggle.prototype.update = function (silent) {
1501
+ * Synchronizes the toggle state with the input element and renders the toggle.
1502
+ */
1503
+ Toggle.prototype.update = function () {
1209
1504
  this.suppressExternalSync = true;
1210
1505
  try {
1211
1506
  this.stateReducer.sync(this.element);
1212
1507
  this.domBuilder.render(this.stateReducer.get());
1213
- if (!silent)
1214
- this.trigger();
1215
1508
  }
1216
1509
  finally {
1217
1510
  this.suppressExternalSync = false;
1218
1511
  }
1219
1512
  };
1220
1513
  /**
1221
- * Triggers the change event on the toggle's input element.
1222
- * @param {boolean} silent A boolean indicating whether to trigger the change event.
1223
- * If the silent parameter is false, this method will trigger the change event.
1224
- */
1225
- Toggle.prototype.trigger = function (silent) {
1226
- if (silent === void 0) { silent = false; }
1227
- if (!silent)
1228
- this.element.dispatchEvent(new Event("change", { bubbles: true }));
1514
+ * Triggers the change event on the toggle's input element and the appropriate toggle event.
1515
+ * This method is called after a toggle action is applied to notify listeners of the state change.
1516
+ * @param {ToggleActionType} action The toggle action that was applied.
1517
+ * @param {ToggleState} state The state of the toggle once the action was applied.
1518
+ */
1519
+ Toggle.prototype.trigger = function (action, state) {
1520
+ this.element.dispatchEvent(new Event("change", { bubbles: true }));
1521
+ var eventName = this.getEventForAction(action, state);
1522
+ var detail = { state: state };
1523
+ this.element.dispatchEvent(new CustomEvent(eventName, {
1524
+ bubbles: true,
1525
+ detail: detail
1526
+ }));
1527
+ };
1528
+ /**
1529
+ * Returns the corresponding toggle event for the given toggle action and state.
1530
+ * This method is used to determine which toggle event to trigger after a toggle action is applied.
1531
+ * @param {ToggleActionType} action The toggle action that was applied.
1532
+ * @param {ToggleState} state The previous state of the toggle before the action was applied.
1533
+ * @returns {ToggleEvents} The corresponding toggle event for the given toggle action and state.
1534
+ */
1535
+ Toggle.prototype.getEventForAction = function (action, state) {
1536
+ switch (action) {
1537
+ case ToggleActionType.ON:
1538
+ return ToggleEvents$1.ON;
1539
+ case ToggleActionType.OFF:
1540
+ return ToggleEvents$1.OFF;
1541
+ case ToggleActionType.INDETERMINATE:
1542
+ return ToggleEvents$1.MIXED;
1543
+ case ToggleActionType.ENABLE:
1544
+ return ToggleEvents$1.ENABLED;
1545
+ case ToggleActionType.DISABLE:
1546
+ return ToggleEvents$1.DISABLED;
1547
+ case ToggleActionType.READONLY:
1548
+ return ToggleEvents$1.READONLY;
1549
+ case ToggleActionType.DETERMINATE:
1550
+ case ToggleActionType.TOGGLE:
1551
+ case ToggleActionType.NEXT:
1552
+ return this.getValueEvent(state);
1553
+ }
1554
+ };
1555
+ /**
1556
+ * Returns the corresponding toggle event for the given toggle state.
1557
+ * This method is used to determine which toggle event to trigger after a toggle action is applied.
1558
+ * @param {ToggleState} state The previous state of the toggle before the action was applied.
1559
+ * @returns {ToggleEvents} The corresponding toggle event for the given toggle state.
1560
+ */
1561
+ Toggle.prototype.getValueEvent = function (state) {
1562
+ switch (state.value) {
1563
+ case ToggleStateValue.ON:
1564
+ return ToggleEvents$1.ON;
1565
+ case ToggleStateValue.OFF:
1566
+ return ToggleEvents$1.OFF;
1567
+ case ToggleStateValue.MIXED:
1568
+ return ToggleEvents$1.MIXED;
1569
+ }
1229
1570
  };
1230
1571
  /**
1231
1572
  * Destroys the toggle element and unbinds all event listeners.
@@ -1305,15 +1646,15 @@
1305
1646
  break;
1306
1647
  case ToggleMethods.ENABLE:
1307
1648
  case ToggleMethods.enable:
1308
- _bsToggle.enable();
1649
+ _bsToggle.enable(silent);
1309
1650
  break;
1310
1651
  case ToggleMethods.DISABLE:
1311
1652
  case ToggleMethods.disable:
1312
- _bsToggle.disable();
1653
+ _bsToggle.disable(silent);
1313
1654
  break;
1314
1655
  case ToggleMethods.READONLY:
1315
1656
  case ToggleMethods.readonly:
1316
- _bsToggle.readonly();
1657
+ _bsToggle.readonly(silent);
1317
1658
  break;
1318
1659
  case ToggleMethods.DESTROY:
1319
1660
  case ToggleMethods.destroy: