@qld-gov-au/qgds-bootstrap5 2.0.9 → 2.0.11

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 (54) hide show
  1. package/.storybook/main.mjs +2 -2
  2. package/dist/assets/components/bs5/banner/banner.hbs +3 -6
  3. package/dist/assets/components/bs5/card/card.hbs +2 -2
  4. package/dist/assets/components/bs5/head/head.hbs +1 -1
  5. package/dist/assets/components/bs5/tag/tag.hbs +1 -1
  6. package/dist/assets/css/qld.bootstrap.css +2 -1
  7. package/dist/assets/css/qld.bootstrap.css.map +2 -2
  8. package/dist/assets/css/qld.bootstrap.legacy.css +2 -1
  9. package/dist/assets/css/qld.bootstrap.legacy.css.map +2 -2
  10. package/dist/assets/js/handlebars.helpers.bundle.js +2 -1
  11. package/dist/assets/js/handlebars.helpers.bundle.js.map +2 -2
  12. package/dist/assets/js/handlebars.init.min.js +9 -11
  13. package/dist/assets/js/handlebars.init.min.js.map +2 -2
  14. package/dist/assets/js/handlebars.partials.js +9 -11
  15. package/dist/assets/js/handlebars.partials.js.map +2 -2
  16. package/dist/assets/js/qld.bootstrap.min.js +6 -5
  17. package/dist/assets/js/qld.bootstrap.min.js.map +4 -4
  18. package/dist/assets/node/handlebars.init.min.js +9 -5
  19. package/dist/assets/node/handlebars.init.min.js.map +2 -2
  20. package/dist/components/bs5/banner/banner.hbs +3 -6
  21. package/dist/components/bs5/card/card.hbs +2 -2
  22. package/dist/components/bs5/head/head.hbs +1 -1
  23. package/dist/components/bs5/tag/tag.hbs +1 -1
  24. package/dist/package.json +1 -1
  25. package/dist/sample-data/card/card.data.json +4 -1
  26. package/dist/sample-data/tag/tag.data.json +149 -143
  27. package/esbuild.js +8 -0
  28. package/package.json +1 -1
  29. package/src/components/bs5/banner/banner.hbs +3 -6
  30. package/src/components/bs5/banner/banner.scss +10 -7
  31. package/src/components/bs5/banner/banner.stories.js +2 -5
  32. package/src/components/bs5/button/button.scss +4 -11
  33. package/src/components/bs5/button/button.stories.js +17 -15
  34. package/src/components/bs5/card/Card.js +31 -2
  35. package/src/components/bs5/card/Card.mdx +4 -0
  36. package/src/components/bs5/card/card--icon-list-footer.stories.js +2 -24
  37. package/src/components/bs5/card/card--multi-action.stories.js +9 -28
  38. package/src/components/bs5/card/card--no-action.stories.js +5 -27
  39. package/src/components/bs5/card/card--single-action.stories.js +4 -33
  40. package/src/components/bs5/card/card.data.json +4 -1
  41. package/src/components/bs5/card/card.hbs +2 -2
  42. package/src/components/bs5/footer/footer_formio.scss +5 -5
  43. package/src/components/bs5/modal/modal.scss +106 -99
  44. package/src/components/bs5/navbar/navbar.functions.js +122 -19
  45. package/src/components/bs5/tag/tag--status.stories.js +1 -0
  46. package/src/components/bs5/tag/tag.data.json +149 -143
  47. package/src/components/bs5/tag/tag.hbs +1 -1
  48. package/src/components/bs5/tag/tag.scss +2 -5
  49. package/src/components/bs5/tag/tag.stories.js +1 -0
  50. package/src/components/bs5/typography/typography.stories.js +1 -1
  51. package/src/css/mixins/focusable.scss +1 -1
  52. package/src/css/mixins/make-icon.scss +1 -1
  53. package/src/js/handlebars.helpers.js +9 -1
  54. package/src/js/utils.js +142 -0
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
+ }