@qld-gov-au/qgds-bootstrap5 2.0.10 → 2.0.12

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 (113) hide show
  1. package/.storybook/main.mjs +2 -2
  2. package/.storybook/preview.js +5 -2
  3. package/dist/assets/components/bs5/banner/banner.hbs +3 -6
  4. package/dist/assets/components/bs5/card/card.hbs +2 -2
  5. package/dist/assets/components/bs5/dateinput/dateinput.hbs +27 -27
  6. package/dist/assets/components/bs5/formcheck/formcheck.hbs +10 -2
  7. package/dist/assets/components/bs5/head/head.hbs +1 -1
  8. package/dist/assets/components/bs5/searchInput/searchInput.hbs +31 -29
  9. package/dist/assets/components/bs5/select/select.hbs +19 -19
  10. package/dist/assets/components/bs5/tag/tag.hbs +1 -1
  11. package/dist/assets/components/bs5/textarea/textarea.hbs +17 -17
  12. package/dist/assets/components/bs5/textbox/textbox.hbs +17 -18
  13. package/dist/assets/css/qld.bootstrap.css +2 -2
  14. package/dist/assets/css/qld.bootstrap.css.map +3 -3
  15. package/dist/assets/css/qld.bootstrap.legacy.css +2 -2
  16. package/dist/assets/css/qld.bootstrap.legacy.css.map +3 -3
  17. package/dist/assets/js/handlebars.helpers.bundle.js +2 -2
  18. package/dist/assets/js/handlebars.helpers.bundle.js.map +2 -2
  19. package/dist/assets/js/handlebars.init.min.js +141 -135
  20. package/dist/assets/js/handlebars.init.min.js.map +2 -2
  21. package/dist/assets/js/handlebars.partials.js +141 -135
  22. package/dist/assets/js/handlebars.partials.js.map +2 -2
  23. package/dist/assets/js/qld.bootstrap.min.js +9 -10
  24. package/dist/assets/js/qld.bootstrap.min.js.map +4 -4
  25. package/dist/assets/node/handlebars.init.min.js +59 -11
  26. package/dist/assets/node/handlebars.init.min.js.map +2 -2
  27. package/dist/components/bs5/banner/banner.hbs +3 -6
  28. package/dist/components/bs5/card/card.hbs +2 -2
  29. package/dist/components/bs5/dateinput/dateinput.hbs +27 -27
  30. package/dist/components/bs5/formcheck/formcheck.hbs +10 -2
  31. package/dist/components/bs5/head/head.hbs +1 -1
  32. package/dist/components/bs5/searchInput/searchInput.hbs +31 -29
  33. package/dist/components/bs5/select/select.hbs +19 -19
  34. package/dist/components/bs5/tag/tag.hbs +1 -1
  35. package/dist/components/bs5/textarea/textarea.hbs +17 -17
  36. package/dist/components/bs5/textbox/textbox.hbs +17 -18
  37. package/dist/package.json +1 -1
  38. package/dist/sample-data/card/card.data.json +4 -1
  39. package/dist/sample-data/dateinput/dateinput.data.json +14 -12
  40. package/dist/sample-data/formcheck/stories/checkbox/checkbox.data.json +4 -5
  41. package/dist/sample-data/formcheck/stories/radio/radio.data.json +4 -4
  42. package/dist/sample-data/searchInput/searchInput.data.json +19 -10
  43. package/dist/sample-data/select/select.data.json +12 -10
  44. package/dist/sample-data/tag/tag.data.json +149 -143
  45. package/dist/sample-data/textarea/textarea.data.json +14 -11
  46. package/dist/sample-data/textbox/textbox.data.json +13 -10
  47. package/package.json +1 -1
  48. package/src/components/bs5/banner/banner.hbs +3 -6
  49. package/src/components/bs5/banner/banner.scss +10 -7
  50. package/src/components/bs5/banner/banner.stories.js +2 -5
  51. package/src/components/bs5/button/button.scss +4 -11
  52. package/src/components/bs5/button/button.stories.js +17 -15
  53. package/src/components/bs5/card/Card.js +31 -2
  54. package/src/components/bs5/card/Card.mdx +4 -0
  55. package/src/components/bs5/card/card--icon-list-footer.stories.js +2 -24
  56. package/src/components/bs5/card/card--multi-action.stories.js +9 -28
  57. package/src/components/bs5/card/card--no-action.stories.js +5 -27
  58. package/src/components/bs5/card/card--single-action.stories.js +4 -33
  59. package/src/components/bs5/card/card.data.json +4 -1
  60. package/src/components/bs5/card/card.hbs +2 -2
  61. package/src/components/bs5/dateinput/Dateinput.js +26 -11
  62. package/src/components/bs5/dateinput/dateinput.data.json +14 -12
  63. package/src/components/bs5/dateinput/dateinput.hbs +27 -27
  64. package/src/components/bs5/footer/footer_formio.scss +5 -5
  65. package/src/components/bs5/formcheck/Formcheck.js +57 -6
  66. package/src/components/bs5/formcheck/_form-variables.scss +131 -0
  67. package/src/components/bs5/formcheck/formcheck.hbs +10 -2
  68. package/src/components/bs5/formcheck/formcheck.scss +229 -66
  69. package/src/components/bs5/formcheck/stories/bootstrap-validation/bootstrap-validation.stories.js +304 -0
  70. package/src/components/bs5/formcheck/stories/checkbox/checkbox.data.json +4 -5
  71. package/src/components/bs5/formcheck/stories/checkbox/checkbox.stories.js +19 -111
  72. package/src/components/bs5/formcheck/stories/radio/radio.data.json +4 -4
  73. package/src/components/bs5/formcheck/stories/radio/radio.stories.js +30 -122
  74. package/src/components/bs5/inpageAlert/inpageAlert.scss +1 -1
  75. package/src/components/bs5/modal/modal.scss +106 -99
  76. package/src/components/bs5/navbar/navbar.functions.js +122 -19
  77. package/src/components/bs5/pageLayout/{ThemeShowcase.stories.js → PaletteShowcase.stories.js} +36 -35
  78. package/src/components/bs5/searchInput/__snapshots__/searchInput.test.js.snap +24 -28
  79. package/src/components/bs5/searchInput/search.functions.js +93 -76
  80. package/src/components/bs5/searchInput/searchInput.data.json +19 -10
  81. package/src/components/bs5/searchInput/searchInput.hbs +31 -29
  82. package/src/components/bs5/searchInput/searchInput.scss +140 -196
  83. package/src/components/bs5/searchInput/searchInput.stories.js +35 -13
  84. package/src/components/bs5/searchInput/searchInput.test.js +5 -1
  85. package/src/components/bs5/select/Select.js +13 -5
  86. package/src/components/bs5/select/Select.stories.js +27 -83
  87. package/src/components/bs5/select/select.data.json +12 -10
  88. package/src/components/bs5/select/select.hbs +19 -19
  89. package/src/components/bs5/tag/tag--status.stories.js +1 -0
  90. package/src/components/bs5/tag/tag.data.json +149 -143
  91. package/src/components/bs5/tag/tag.hbs +1 -1
  92. package/src/components/bs5/tag/tag.scss +2 -5
  93. package/src/components/bs5/tag/tag.stories.js +1 -0
  94. package/src/components/bs5/textarea/Textarea.js +13 -5
  95. package/src/components/bs5/textarea/Textarea.stories.js +29 -55
  96. package/src/components/bs5/textarea/textarea.data.json +14 -11
  97. package/src/components/bs5/textarea/textarea.hbs +17 -17
  98. package/src/components/bs5/textbox/Textbox.js +16 -5
  99. package/src/components/bs5/textbox/Textbox.stories.js +26 -51
  100. package/src/components/bs5/textbox/textInput.scss +12 -232
  101. package/src/components/bs5/textbox/textbox.data.json +13 -10
  102. package/src/components/bs5/textbox/textbox.hbs +17 -18
  103. package/src/components/bs5/typography/typography.stories.js +1 -1
  104. package/src/css/functions/_index.scss +2 -0
  105. package/src/css/functions/remify.scss +32 -0
  106. package/src/css/functions/snap-line-height.scss +7 -0
  107. package/src/css/main.scss +1 -1
  108. package/src/css/mixins/focusable.scss +4 -1
  109. package/src/css/mixins/make-icon.scss +1 -1
  110. package/src/css/{qld-theme.scss → qld-palettes.scss} +30 -23
  111. package/src/js/handlebars.helpers.js +9 -1
  112. package/src/js/utils.js +142 -0
  113. package/src/components/bs5/formcheck/_formcheck.stories.bak.js +0 -432
@@ -1,32 +1,31 @@
1
1
  <!-- QGDS Component: Textbox -->
2
2
 
3
- <!-- Label for the first input field -->
4
3
  <label class="qld-text-input-label {{#if isRequired}}field-required{{/if}} {{#if isDisabled}}field-disabled{{/if}}"
5
- for="example-1">
4
+ for="{{id}}">
6
5
  {{label-text}}
7
6
  {{#if optional-text}}
8
7
  <span class="label-text-optional">({{optional-text}})</span>
9
8
  {{/if}}
10
9
  </label>
11
10
 
12
- <!-- Hint text for the first input field -->
13
11
  {{#if hint-text}}
14
- <span class="qld-hint-text" id="example-1-hint">{{hint-text}}</span>
12
+ <span class="qld-hint-text" id="{{id}}-hint">{{hint-text}}</span>
15
13
  {{/if}}
16
14
 
17
- {{#contains "qld-input-success" customClass}}
18
- <span id="text-field-success" class="qld-input-success">
19
- {{successMessageText}}
20
- </span>
21
- {{/contains}}
15
+ {{#contains "qld-input-success" customClass}}{{! legacy support for feedback classes `qld-input-success`}}
16
+ <span class="qld-input-success">{{successMessageText}}</span>
17
+ {{else}}
18
+ {{#if successMessageText}}{{! updated bootstrap style classes - `valid-feedback`}}
19
+ <div class="valid-feedback">{{successMessageText}}</div>
20
+ {{/if}}{{/contains}}
22
21
 
23
- {{#contains "qld-input-error" customClass}}
24
- <span id="text-field-error" class="qld-input-error">
25
- {{errorMessageText}}
26
- </span>
27
- {{/contains}}
22
+ {{#contains "qld-input-error" customClass}}{{! legacy support for feedback classes `qld-input-error`}}
23
+ <span class="qld-input-error">{{errorMessageText}}</span>
24
+ {{else}}
25
+ {{#if errorMessageText}}{{! updated bootstrap style classes - `invalid-feedback`}}
26
+ <div class="invalid-feedback">{{errorMessageText}}</div>
27
+ {{/if}}{{/contains}}
28
28
 
29
- <!-- First text input field, described by the hint text above -->
30
- <input id="example-1" class="qld-text-input form-control {{customClass}} {{#if isFilled}}form-style-filled{{/if}}"
31
- type="text" placeholder="{{placeholder}}" aria-label="Text input example" {{#if isDisabled}}disabled{{/if}} {{#if
32
- isRequired}}required aria-required="true" {{/if}} />
29
+ <input id={{id}} {{#if value}}value="{{value}}"{{/if}} class="form-control {{customClass}} {{#if isFilled}}is-filled{{/if}} {{#if isValid}}is-valid{{else}}{{#ifCond isValid "===" false}}is-invalid{{/ifCond}}{{/if}}"
30
+ type="text" placeholder="{{placeholder}}" {{#if isDisabled}}disabled{{/if}} {{#if
31
+ isRequired}}required{{/if}} {{#if hint-text}}aria-describedby="{{id}}-hint"{{/if}} />
@@ -69,8 +69,8 @@ export default {
69
69
  },
70
70
  ],
71
71
  backgrounds: { disable: false },
72
- globals: { backgrounds: { value: "default" } },
73
72
  },
73
+ globals: { backgrounds: { value: "default" } },
74
74
  };
75
75
 
76
76
  /**
@@ -1 +1,3 @@
1
1
  @forward "in-list";
2
+ @forward "remify";
3
+ @forward "snap-line-height";
@@ -0,0 +1,32 @@
1
+ @use "sass:math";
2
+ @use "sass:meta";
3
+ @use "sass:list";
4
+
5
+ ///
6
+ /// Easily convert px to rems
7
+ /// @param {Number} $value The px value to convert to rem. May be unitless or supplied as px units.
8
+ /// @param {Number} $baseline [16px] The assumed px value of 1rem.
9
+ /// @todo support complex property values eg: `border: remify(4px 8px 12px 2rem)`;
10
+ @function remify($value, $baseline: 16px) {
11
+ @if (math.is-unitless($baseline)) {
12
+ $baseline-px: $baseline * 1px;
13
+ }
14
+
15
+ @if (math.unit($baseline) != "px") {
16
+ @error "parameter $baseline may only use px units";
17
+ }
18
+
19
+ @if (meta.type-of($value) == "list") {
20
+ $result: ();
21
+ @each $item in $value {
22
+ $result: list.append($result, remify($item));
23
+ }
24
+ @return $result;
25
+ }
26
+
27
+ @if (math.is-unitless($value)) {
28
+ $value: $value * 1px;
29
+ }
30
+
31
+ @return (math.div($value, $baseline) * 1rem);
32
+ }
@@ -0,0 +1,7 @@
1
+ @use "remify" as *;
2
+ ///
3
+ /// Calculate a lineheight snapped to nearest 4px value, based on current font size
4
+ /// The returned value is absolute, so only use on leaf nodes where relative line-height value doesn't need to cascade.
5
+ @function snap-line-height($ideal-line-height: 1.4, $grid-size: 4px) {
6
+ @return round(nearest, calc(1em * $ideal-line-height), remify($grid-size));
7
+ }
package/src/css/main.scss CHANGED
@@ -100,7 +100,7 @@ $enable-dark-mode: true;
100
100
  @import "../../node_modules/bootstrap/scss/utilities/api";
101
101
 
102
102
  // Themes
103
- @import "qld-theme";
103
+ @import "qld-palettes";
104
104
 
105
105
  //8. QLD Design System typography (bootstrap overrides and custom). Please maintain naming consistency.
106
106
  @import "./qld-type";
@@ -4,6 +4,7 @@
4
4
  /// @param {Boolean} $isFocusWithin [false] - Optionally apply styles via :focus-within rather than :focus-visible
5
5
  /// @param {String} $outlineWidth: [3px] The width of the outline.
6
6
  /// @param {String} $customSelector [null] - If passed, will override $isFocusWithin and apply focus styles instead to a custom selector string. @see SearchInput
7
+ /// @content
7
8
 
8
9
  @mixin focusable(
9
10
  $offsetOutline: true,
@@ -35,9 +36,11 @@
35
36
  style: solid;
36
37
  color: var(
37
38
  --qld-focus-color,
38
- #0085b3
39
+ #002e85
39
40
  ); // --qld-focus-color defined in qld-type.scss
40
41
  offset: $offsetOutline;
41
42
  }
43
+
44
+ @content;
42
45
  }
43
46
  }
@@ -11,7 +11,7 @@ $prefix: "qld-" !default;
11
11
  /// @param {String} $name [null] - The name of icon
12
12
  /// @param {String} $size ["sm"] - The icon size, valid values are "xs" | "sm" | "md" | "lg" | "xl" | "xxl"
13
13
  /// @param {String | null} $pseudo [null] - Whether the mixin applies styles as a pseudo-element. Valid string values are "before" | "after"
14
- /// @param {false} $is-inline - Applies extra styles to assist with vertical alignent when used inline with text.
14
+ /// @param {Boolean} $is-inline [false] - Applies extra styles to assist with vertical alignent when used inline with text.
15
15
  /// @param {Boolean} $include-base [true] - Apply all base styles. Set to false when reusing this mixin to create modifier classes where base styles are already applied (For instance qld-icon-{name})
16
16
  /// @content
17
17
  @mixin make-icon(
@@ -2,7 +2,7 @@
2
2
  // Provides complete theme styling including color, background-color, and CSS variables
3
3
 
4
4
  // Theme properties mixins to avoid duplication
5
- @mixin light-theme-properties {
5
+ %qld-palette-default {
6
6
  background-color: var(--qld-body-bg);
7
7
  color: var(--qld-body-color);
8
8
  --qld-action-icon-color: var(--qld-light-action-secondary);
@@ -15,7 +15,6 @@
15
15
  --qld-headings-color: #{$headings-color};
16
16
  --qld-link-color: #{$link-color};
17
17
  --qld-link-color-rgb: #{to-rgb($link-color)};
18
- --qld-link-font-weight: 400;
19
18
  --qld-link-hover-color: var(--qld-link-color);
20
19
  --qld-link-visited-color: #{$color-default-color-light-link-visited};
21
20
  --qld-link-hover-color-rgb: var(--qld-link-color-rgb);
@@ -30,12 +29,26 @@
30
29
  --qld-selection-color: var(--qld-white);
31
30
  }
32
31
 
33
- @mixin dark-theme-properties {
32
+ %qld-palette-light {
33
+ @extend %qld-palette-default;
34
+ --qld-body-bg: var(--qld-light-background);
35
+ --qld-border-color: var(--#{$prefix}color-default-color-light-border-light);
36
+ }
37
+
38
+ %qld-palette-alt {
39
+ @extend %qld-palette-default;
40
+ --qld-body-bg: var(--qld-light-alt-background);
41
+ --qld-border-color: var(--qld-soft-grey);
42
+ }
43
+
44
+ %qld-palette-dark {
34
45
  background-color: var(--qld-body-bg);
35
46
  color: var(--qld-body-color);
36
47
  --qld-action-icon-color: var(--qld-dark-action-secondary);
37
48
  --qld-action-icon-hover-color: var(--qld-dark-action-secondary-hover);
49
+ --qld-body-bg: var(--qld-dark-background);
38
50
  --qld-body-color: #fff;
51
+ --qld-border-color: var(--qld-dark-border);
39
52
  --qld-focus-color: var(--qld-dark-focus);
40
53
  --qld-headings-color: #{$color-default-color-dark-text-heading};
41
54
  --qld-link-color: #{$color-default-color-dark-link-default};
@@ -54,42 +67,36 @@
54
67
  --qld-selection-color: var(--qld-brand-primary);
55
68
  }
56
69
 
70
+ %qld-palette-dark-alt {
71
+ @extend %qld-palette-dark;
72
+ --qld-body-bg: var(--qld-dark-alt-background);
73
+ --qld-border-color: var(--qld-dark-alt-border);
74
+ }
75
+
57
76
  // Default theme (inherits :root variables) and root variables
58
77
  :root,
59
78
  .default {
60
- @include light-theme-properties;
79
+ @extend %qld-palette-default;
61
80
  }
62
81
 
63
82
  // Light theme
64
83
  .light {
65
- @include light-theme-properties;
66
- --qld-body-bg: var(--qld-light-background);
67
- --qld-border-color: var(--#{$prefix}color-default-color-light-border-light);
84
+ @extend %qld-palette-light;
68
85
  }
69
86
 
70
87
  // Alt theme (light alternative)
71
88
  .alt {
72
- @include light-theme-properties;
73
- --qld-body-bg: var(--qld-light-alt-background);
74
- --qld-border-color: var(--qld-soft-grey);
89
+ @extend %qld-palette-alt;
75
90
  }
76
91
 
77
92
  // Dark theme
78
- .dark {
79
- @include dark-theme-properties;
80
- --qld-body-bg: var(--qld-dark-background);
81
- --qld-border-color: var(--qld-dark-border);
93
+ // Bootstrap dark mode support
94
+ .dark,
95
+ :root[data-bs-theme="dark"] {
96
+ @extend %qld-palette-dark;
82
97
  }
83
98
 
84
99
  // Dark alt theme
85
100
  .dark-alt {
86
- @include dark-theme-properties;
87
- --qld-body-bg: var(--qld-dark-alt-background);
88
- --qld-border-color: var(--qld-dark-alt-border);
89
- }
90
-
91
- // Bootstrap dark mode support
92
- :root[data-bs-theme="dark"] {
93
- @include dark-theme-properties;
94
- --qld-body-bg: var(--qld-brand-primary);
101
+ @extend %qld-palette-dark-alt;
95
102
  }
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  *
5
5
  * @param {*} v1 The left value
6
- * @param { "==" | "===" | "!=" | "!==" | "<" | "<=" | ">" | ">=" | "&&" | "||" | "contains"} operator the operator to handle comparison
6
+ * @param { "==" | "===" | "!=" | "!==" | "<" | "<=" | ">" | ">=" | "&&" | "||" | "in" | "contains"} operator the operator to handle comparison
7
7
  * @param {*} v2 The right value
8
8
  * @returns {Boolean} the result of comparison
9
9
  * @example
@@ -92,6 +92,14 @@ export default function handlebarsHelpers(handlebars) {
92
92
  return args.some((arg) => !!arg) ? options.fn(this) : options.inverse(this);
93
93
  });
94
94
 
95
+ // ifAll - {{{#ifAll variable1 variable2 variable3 variable4 etc}}, if all true return true
96
+ handlebars.registerHelper("ifAll", function (...args) {
97
+ const options = args.pop(); // The last argument is the options object
98
+ return args.every((arg) => !!arg)
99
+ ? options.fn(this)
100
+ : options.inverse(this);
101
+ });
102
+
95
103
  // now - return current timestamp i.e {{now}}
96
104
  handlebars.registerHelper("now", function () {
97
105
  return new Date().toISOString();
package/src/js/utils.js CHANGED
@@ -47,3 +47,145 @@ export function isFocusable(element) {
47
47
  }
48
48
  return false;
49
49
  }
50
+
51
+ /**
52
+ * Get all focusable elements within a container
53
+ * @param {HTMLElement} container The container element to search within
54
+ * @returns {HTMLElement[]} Array of focusable elements
55
+ */
56
+ export function getFocusableElements(container) {
57
+ if (!container) return [];
58
+
59
+ const allElements = container.querySelectorAll("*");
60
+ return Array.from(allElements).filter((el) => isFocusable(el));
61
+ }
62
+
63
+ /**
64
+ * Create a focus trap for accessibility
65
+ * Traps keyboard focus within a container element (e.g., modal, navbar, menu)
66
+ * @param {HTMLElement} container The container element to trap focus within
67
+ * @param {Object} options Configuration options
68
+ * @param {HTMLElement} options.returnFocusElement Element to return focus to when deactivated
69
+ * @param {Function} options.onEscape Callback function when Escape key is pressed
70
+ * @returns {Object} Focus trap controller with activate, deactivate, and update methods
71
+ */
72
+ export function createFocusTrap(container, options = {}) {
73
+ if (!container) {
74
+ throw new Error("Container element is required for focus trap");
75
+ }
76
+
77
+ const { returnFocusElement, onEscape } = options;
78
+ let isActive = false;
79
+ let focusableElements = [];
80
+ let previousActiveElement = null;
81
+
82
+ /**
83
+ * Update the list of focusable elements
84
+ */
85
+ function updateFocusableElements() {
86
+ focusableElements = getFocusableElements(container);
87
+ }
88
+
89
+ /**
90
+ * Handle Tab key navigation within the trap
91
+ */
92
+ function handleTabKey(event) {
93
+ if (!isActive || focusableElements.length === 0) return;
94
+
95
+ const firstElement = focusableElements[0];
96
+ const lastElement = focusableElements[focusableElements.length - 1];
97
+ const activeElement = document.activeElement;
98
+
99
+ // Shift + Tab (backward)
100
+ if (event.shiftKey) {
101
+ if (
102
+ activeElement === firstElement ||
103
+ !container.contains(activeElement)
104
+ ) {
105
+ event.preventDefault();
106
+ lastElement.focus();
107
+ }
108
+ }
109
+ // Tab (forward)
110
+ else {
111
+ if (activeElement === lastElement || !container.contains(activeElement)) {
112
+ event.preventDefault();
113
+ firstElement.focus();
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Handle Escape key to close/deactivate
120
+ */
121
+ function handleEscapeKey(event) {
122
+ if (!isActive) return;
123
+
124
+ if (event.key === "Escape" || event.key === "Esc") {
125
+ event.preventDefault();
126
+ if (typeof onEscape === "function") {
127
+ onEscape();
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Handle all keyboard events
134
+ */
135
+ function handleKeyDown(event) {
136
+ if (event.key === "Tab") {
137
+ handleTabKey(event);
138
+ } else if (event.key === "Escape" || event.key === "Esc") {
139
+ handleEscapeKey(event);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Activate the focus trap
145
+ */
146
+ function activate() {
147
+ if (isActive) return;
148
+
149
+ // Store the currently focused element to return focus later
150
+ previousActiveElement = document.activeElement;
151
+
152
+ // Update focusable elements
153
+ updateFocusableElements();
154
+
155
+ // Add keyboard event listener
156
+ document.addEventListener("keydown", handleKeyDown);
157
+
158
+ // Focus the first focusable element
159
+ if (focusableElements.length > 0) {
160
+ focusableElements[0].focus();
161
+ }
162
+
163
+ isActive = true;
164
+ }
165
+
166
+ /**
167
+ * Deactivate the focus trap
168
+ */
169
+ function deactivate() {
170
+ if (!isActive) return;
171
+
172
+ // Remove keyboard event listener
173
+ document.removeEventListener("keydown", handleKeyDown);
174
+
175
+ // Return focus to the element that had focus before activation
176
+ const elementToFocus = returnFocusElement || previousActiveElement;
177
+ if (elementToFocus && typeof elementToFocus.focus === "function") {
178
+ elementToFocus.focus();
179
+ }
180
+
181
+ isActive = false;
182
+ previousActiveElement = null;
183
+ }
184
+
185
+ return {
186
+ activate,
187
+ deactivate,
188
+ update: updateFocusableElements,
189
+ isActive: () => isActive,
190
+ };
191
+ }