ux4g-components-web 1.0.0 → 1.1.0

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 ADDED
@@ -0,0 +1,75 @@
1
+ # ux4g-components-web
2
+
3
+ The CSS system, design tokens, utilities, and shared Class_Builder types for the UX4G Design System.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install ux4g-components-web
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### CSS Bundle
14
+
15
+ Import the full CSS bundle in your application entry point:
16
+
17
+ ```ts
18
+ // JavaScript/TypeScript
19
+ import 'ux4g-components-web/styles.css';
20
+ ```
21
+
22
+ ```html
23
+ <!-- HTML -->
24
+ <link rel="stylesheet" href="node_modules/ux4g-components-web/styles/ux4g.css" />
25
+ ```
26
+
27
+ ```css
28
+ /* CSS */
29
+ @import 'ux4g-components-web/styles.css';
30
+ ```
31
+
32
+ The bundle includes:
33
+ - Design tokens (CSS custom properties)
34
+ - Semantic layer (semantic aliases)
35
+ - All 27 utility modules
36
+ - Approved component CSS
37
+ - Layout utilities
38
+ - Cascade fixes
39
+
40
+ ### Shared Types (Class_Builders)
41
+
42
+ Framework wrapper packages import Class_Builder functions from this package:
43
+
44
+ ```ts
45
+ import { buildButtonClasses, ButtonVariant, ButtonSize } from 'ux4g-components-web/types';
46
+
47
+ const classes = buildButtonClasses('primary', 'md', false, false);
48
+ // → "ux4g-btn-primary ux4g-btn-md"
49
+ ```
50
+
51
+ ### Utility Classes
52
+
53
+ Use utility classes directly in HTML — no wrapper component needed:
54
+
55
+ ```html
56
+ <div class="ux4g-d-flex ux4g-gap-4 ux4g-p-m ux4g-bg-primary-soft ux4g-radius-md">
57
+ <span class="ux4g-body-m-default ux4g-text-neutral-primary">Content</span>
58
+ </div>
59
+ ```
60
+
61
+ ## What's Included
62
+
63
+ | Export | Description |
64
+ |--------|-------------|
65
+ | `ux4g-components-web/styles.css` | Pre-built CSS bundle (~7MB, fonts embedded as base64) |
66
+ | `ux4g-components-web/types` | Class_Builder functions and TypeScript types |
67
+
68
+ ## Related Packages
69
+
70
+ - [`ux4g-components-react`](https://www.npmjs.com/package/ux4g-components-react) — React wrapper components
71
+ - [`ux4g-components-angular`](https://www.npmjs.com/package/ux4g-components-angular) — Angular wrapper components
72
+
73
+ ## License
74
+
75
+ MIT
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index');
4
+
5
+ /**
6
+ * UX4G Runtime Auto-Bootstrap
7
+ *
8
+ * Side-effect-only module that triggers runtime initialization on import.
9
+ * Framework packages use this as:
10
+ * import 'ux4g-components-web/runtime/bootstrap';
11
+ *
12
+ * Safe to import multiple times — initRuntime() uses a singleton guard
13
+ * (window.__UX4G_RUNTIME_INITIALIZED__) to ensure initialization happens
14
+ * exactly once.
15
+ */
16
+ index.initRuntime();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { initRuntime } from './index';
2
+
3
+ /**
4
+ * UX4G Runtime Auto-Bootstrap
5
+ *
6
+ * Side-effect-only module that triggers runtime initialization on import.
7
+ * Framework packages use this as:
8
+ * import 'ux4g-components-web/runtime/bootstrap';
9
+ *
10
+ * Safe to import multiple times — initRuntime() uses a singleton guard
11
+ * (window.__UX4G_RUNTIME_INITIALIZED__) to ensure initialization happens
12
+ * exactly once.
13
+ */
14
+ initRuntime();
@@ -0,0 +1,300 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * UX4G Runtime Module
5
+ *
6
+ * Framework-agnostic runtime that encapsulates all interactive behaviors
7
+ * previously delivered via CDN scripts (ux4g.js, ux4g-custom.js).
8
+ *
9
+ * Behaviors: dropdown toggling, modal management, tooltip positioning,
10
+ * accordion expand/collapse, carousel sliding, drawer open/close.
11
+ *
12
+ * Uses a singleton guard (window.__UX4G_RUNTIME_INITIALIZED__) to ensure
13
+ * initialization happens exactly once, even when multiple components import
14
+ * the bootstrap module.
15
+ */
16
+ /** Registry of all active event listeners for cleanup */
17
+ let activeListeners = [];
18
+ /** Registry of active MutationObservers for cleanup */
19
+ let activeObservers = [];
20
+ // ─── Helper: register a listener for later cleanup ───────────────────────────
21
+ function addListener(target, event, handler) {
22
+ target.addEventListener(event, handler);
23
+ activeListeners.push({ target, event, handler });
24
+ }
25
+ // ─── Dropdown Toggling ───────────────────────────────────────────────────────
26
+ function initDropdowns() {
27
+ const handler = (e) => {
28
+ const target = e.target;
29
+ const trigger = target.closest('[data-ux4g-dropdown-toggle]');
30
+ if (trigger) {
31
+ e.preventDefault();
32
+ const dropdown = trigger.closest('.ux4g-dropdown');
33
+ if (dropdown) {
34
+ const isOpen = dropdown.classList.contains('is-open');
35
+ // Close all other open dropdowns
36
+ document.querySelectorAll('.ux4g-dropdown.is-open').forEach((el) => {
37
+ if (el !== dropdown)
38
+ el.classList.remove('is-open');
39
+ });
40
+ dropdown.classList.toggle('is-open', !isOpen);
41
+ }
42
+ return;
43
+ }
44
+ // Close dropdowns when clicking outside
45
+ if (!target.closest('.ux4g-dropdown')) {
46
+ document.querySelectorAll('.ux4g-dropdown.is-open').forEach((el) => {
47
+ el.classList.remove('is-open');
48
+ });
49
+ }
50
+ };
51
+ addListener(document, 'click', handler);
52
+ }
53
+ // ─── Modal Management ────────────────────────────────────────────────────────
54
+ function initModals() {
55
+ const openHandler = (e) => {
56
+ const target = e.target;
57
+ const trigger = target.closest('[data-ux4g-modal-open]');
58
+ if (trigger) {
59
+ e.preventDefault();
60
+ const modalId = trigger.getAttribute('data-ux4g-modal-open');
61
+ if (modalId) {
62
+ const backdrop = document.getElementById(modalId);
63
+ if (backdrop) {
64
+ backdrop.classList.add('is-open');
65
+ document.body.style.overflow = 'hidden';
66
+ }
67
+ }
68
+ }
69
+ };
70
+ const closeHandler = (e) => {
71
+ const target = e.target;
72
+ // Close button inside modal
73
+ const closeBtn = target.closest('[data-ux4g-modal-close]');
74
+ if (closeBtn) {
75
+ e.preventDefault();
76
+ const backdrop = closeBtn.closest('.ux4g-modal-backdrop');
77
+ if (backdrop) {
78
+ backdrop.classList.remove('is-open');
79
+ document.body.style.overflow = '';
80
+ }
81
+ return;
82
+ }
83
+ // Click on backdrop (outside modal box) closes modal
84
+ if (target.classList.contains('ux4g-modal-backdrop') && target.classList.contains('is-open')) {
85
+ target.classList.remove('is-open');
86
+ document.body.style.overflow = '';
87
+ }
88
+ };
89
+ const keyHandler = (e) => {
90
+ if (e.key === 'Escape') {
91
+ const openModal = document.querySelector('.ux4g-modal-backdrop.is-open');
92
+ if (openModal) {
93
+ openModal.classList.remove('is-open');
94
+ document.body.style.overflow = '';
95
+ }
96
+ }
97
+ };
98
+ addListener(document, 'click', openHandler);
99
+ addListener(document, 'click', closeHandler);
100
+ addListener(document, 'keydown', keyHandler);
101
+ }
102
+ // ─── Tooltip Positioning ─────────────────────────────────────────────────────
103
+ function initTooltips() {
104
+ const showHandler = (e) => {
105
+ const target = e.target;
106
+ const trigger = target.closest('[data-ux4g-tooltip]');
107
+ if (trigger) {
108
+ const tooltipId = trigger.getAttribute('data-ux4g-tooltip');
109
+ if (tooltipId) {
110
+ const tooltip = document.getElementById(tooltipId);
111
+ if (tooltip) {
112
+ tooltip.classList.add('show');
113
+ }
114
+ }
115
+ }
116
+ };
117
+ const hideHandler = (e) => {
118
+ const target = e.target;
119
+ const trigger = target.closest('[data-ux4g-tooltip]');
120
+ if (trigger) {
121
+ const tooltipId = trigger.getAttribute('data-ux4g-tooltip');
122
+ if (tooltipId) {
123
+ const tooltip = document.getElementById(tooltipId);
124
+ if (tooltip) {
125
+ tooltip.classList.remove('show');
126
+ }
127
+ }
128
+ }
129
+ };
130
+ addListener(document, 'mouseenter', showHandler);
131
+ addListener(document, 'mouseleave', hideHandler);
132
+ addListener(document, 'focusin', showHandler);
133
+ addListener(document, 'focusout', hideHandler);
134
+ }
135
+ // ─── Accordion Expand/Collapse ───────────────────────────────────────────────
136
+ function initAccordions() {
137
+ const handler = (e) => {
138
+ const target = e.target;
139
+ const trigger = target.closest('[data-ux4g-accordion-toggle]');
140
+ if (trigger) {
141
+ e.preventDefault();
142
+ const item = trigger.closest('.ux4g-accordion-item');
143
+ if (item) {
144
+ const isExpanded = item.classList.contains('is-expanded');
145
+ item.classList.toggle('is-expanded', !isExpanded);
146
+ // Update aria-expanded attribute
147
+ trigger.setAttribute('aria-expanded', String(!isExpanded));
148
+ // Toggle the panel visibility
149
+ const panel = item.querySelector('.ux4g-accordion-panel');
150
+ if (panel) {
151
+ panel.style.display = isExpanded ? 'none' : '';
152
+ }
153
+ }
154
+ }
155
+ };
156
+ addListener(document, 'click', handler);
157
+ }
158
+ // ─── Carousel Sliding ────────────────────────────────────────────────────────
159
+ function initCarousels() {
160
+ const handler = (e) => {
161
+ const target = e.target;
162
+ // Next button
163
+ const nextBtn = target.closest('[data-ux4g-carousel-next]');
164
+ if (nextBtn) {
165
+ e.preventDefault();
166
+ const carousel = nextBtn.closest('.ux4g-carousel');
167
+ if (carousel) {
168
+ const items = carousel.querySelectorAll('.ux4g-carousel-item');
169
+ const activeItem = carousel.querySelector('.ux4g-carousel-item.active');
170
+ if (activeItem && items.length > 0) {
171
+ const currentIndex = Array.from(items).indexOf(activeItem);
172
+ const nextIndex = (currentIndex + 1) % items.length;
173
+ activeItem.classList.remove('active');
174
+ items[nextIndex].classList.add('active');
175
+ }
176
+ }
177
+ return;
178
+ }
179
+ // Previous button
180
+ const prevBtn = target.closest('[data-ux4g-carousel-prev]');
181
+ if (prevBtn) {
182
+ e.preventDefault();
183
+ const carousel = prevBtn.closest('.ux4g-carousel');
184
+ if (carousel) {
185
+ const items = carousel.querySelectorAll('.ux4g-carousel-item');
186
+ const activeItem = carousel.querySelector('.ux4g-carousel-item.active');
187
+ if (activeItem && items.length > 0) {
188
+ const currentIndex = Array.from(items).indexOf(activeItem);
189
+ const prevIndex = (currentIndex - 1 + items.length) % items.length;
190
+ activeItem.classList.remove('active');
191
+ items[prevIndex].classList.add('active');
192
+ }
193
+ }
194
+ }
195
+ };
196
+ addListener(document, 'click', handler);
197
+ }
198
+ // ─── Drawer Open/Close ───────────────────────────────────────────────────────
199
+ function initDrawers() {
200
+ const openHandler = (e) => {
201
+ const target = e.target;
202
+ const trigger = target.closest('[data-ux4g-drawer-open]');
203
+ if (trigger) {
204
+ e.preventDefault();
205
+ const drawerId = trigger.getAttribute('data-ux4g-drawer-open');
206
+ if (drawerId) {
207
+ const drawer = document.getElementById(drawerId);
208
+ if (drawer) {
209
+ drawer.classList.add('ux4g-drawer-open');
210
+ document.body.style.overflow = 'hidden';
211
+ }
212
+ }
213
+ }
214
+ };
215
+ const closeHandler = (e) => {
216
+ const target = e.target;
217
+ const closeBtn = target.closest('[data-ux4g-drawer-close]');
218
+ if (closeBtn) {
219
+ e.preventDefault();
220
+ const drawer = closeBtn.closest('.ux4g-drawer');
221
+ if (drawer) {
222
+ drawer.classList.remove('ux4g-drawer-open');
223
+ document.body.style.overflow = '';
224
+ }
225
+ }
226
+ };
227
+ const keyHandler = (e) => {
228
+ if (e.key === 'Escape') {
229
+ const openDrawer = document.querySelector('.ux4g-drawer.ux4g-drawer-open');
230
+ if (openDrawer) {
231
+ openDrawer.classList.remove('ux4g-drawer-open');
232
+ document.body.style.overflow = '';
233
+ }
234
+ }
235
+ };
236
+ addListener(document, 'click', openHandler);
237
+ addListener(document, 'click', closeHandler);
238
+ addListener(document, 'keydown', keyHandler);
239
+ }
240
+ // ─── Public API ──────────────────────────────────────────────────────────────
241
+ /**
242
+ * Initialize the UX4G runtime.
243
+ *
244
+ * Encapsulates all interactive behaviors from the CDN scripts (ux4g.js, ux4g-custom.js):
245
+ * - Dropdown toggling
246
+ * - Modal management (open/close/escape/backdrop click)
247
+ * - Tooltip positioning (show/hide on hover/focus)
248
+ * - Accordion expand/collapse
249
+ * - Carousel sliding (next/prev)
250
+ * - Drawer open/close
251
+ *
252
+ * Uses a singleton guard to ensure initialization happens exactly once.
253
+ * Safe for SSR — no-ops when window/document are unavailable.
254
+ */
255
+ function initRuntime() {
256
+ // Guard against non-browser environments (SSR/Node.js)
257
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
258
+ return;
259
+ }
260
+ // Singleton guard: prevent duplicate initialization
261
+ if (window.__UX4G_RUNTIME_INITIALIZED__) {
262
+ return;
263
+ }
264
+ // Initialize all interactive behaviors
265
+ initDropdowns();
266
+ initModals();
267
+ initTooltips();
268
+ initAccordions();
269
+ initCarousels();
270
+ initDrawers();
271
+ // Mark as initialized
272
+ window.__UX4G_RUNTIME_INITIALIZED__ = true;
273
+ }
274
+ /**
275
+ * Destroy the UX4G runtime.
276
+ *
277
+ * Resets the singleton guard and removes all event listeners.
278
+ * Useful for testing and SSR cleanup.
279
+ */
280
+ function destroyRuntime() {
281
+ // Guard against non-browser environments
282
+ if (typeof window === 'undefined') {
283
+ return;
284
+ }
285
+ // Remove all registered event listeners
286
+ for (const { target, event, handler } of activeListeners) {
287
+ target.removeEventListener(event, handler);
288
+ }
289
+ activeListeners = [];
290
+ // Disconnect all observers
291
+ for (const observer of activeObservers) {
292
+ observer.disconnect();
293
+ }
294
+ activeObservers = [];
295
+ // Reset the singleton guard
296
+ window.__UX4G_RUNTIME_INITIALIZED__ = undefined;
297
+ }
298
+
299
+ exports.destroyRuntime = destroyRuntime;
300
+ exports.initRuntime = initRuntime;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * UX4G Runtime Module
3
+ *
4
+ * Framework-agnostic runtime that encapsulates all interactive behaviors
5
+ * previously delivered via CDN scripts (ux4g.js, ux4g-custom.js).
6
+ *
7
+ * Behaviors: dropdown toggling, modal management, tooltip positioning,
8
+ * accordion expand/collapse, carousel sliding, drawer open/close.
9
+ *
10
+ * Uses a singleton guard (window.__UX4G_RUNTIME_INITIALIZED__) to ensure
11
+ * initialization happens exactly once, even when multiple components import
12
+ * the bootstrap module.
13
+ */
14
+ declare global {
15
+ interface Window {
16
+ __UX4G_RUNTIME_INITIALIZED__?: boolean;
17
+ }
18
+ }
19
+ /**
20
+ * Initialize the UX4G runtime.
21
+ *
22
+ * Encapsulates all interactive behaviors from the CDN scripts (ux4g.js, ux4g-custom.js):
23
+ * - Dropdown toggling
24
+ * - Modal management (open/close/escape/backdrop click)
25
+ * - Tooltip positioning (show/hide on hover/focus)
26
+ * - Accordion expand/collapse
27
+ * - Carousel sliding (next/prev)
28
+ * - Drawer open/close
29
+ *
30
+ * Uses a singleton guard to ensure initialization happens exactly once.
31
+ * Safe for SSR — no-ops when window/document are unavailable.
32
+ */
33
+ export declare function initRuntime(): void;
34
+ /**
35
+ * Destroy the UX4G runtime.
36
+ *
37
+ * Resets the singleton guard and removes all event listeners.
38
+ * Useful for testing and SSR cleanup.
39
+ */
40
+ export declare function destroyRuntime(): void;
@@ -0,0 +1,297 @@
1
+ /**
2
+ * UX4G Runtime Module
3
+ *
4
+ * Framework-agnostic runtime that encapsulates all interactive behaviors
5
+ * previously delivered via CDN scripts (ux4g.js, ux4g-custom.js).
6
+ *
7
+ * Behaviors: dropdown toggling, modal management, tooltip positioning,
8
+ * accordion expand/collapse, carousel sliding, drawer open/close.
9
+ *
10
+ * Uses a singleton guard (window.__UX4G_RUNTIME_INITIALIZED__) to ensure
11
+ * initialization happens exactly once, even when multiple components import
12
+ * the bootstrap module.
13
+ */
14
+ /** Registry of all active event listeners for cleanup */
15
+ let activeListeners = [];
16
+ /** Registry of active MutationObservers for cleanup */
17
+ let activeObservers = [];
18
+ // ─── Helper: register a listener for later cleanup ───────────────────────────
19
+ function addListener(target, event, handler) {
20
+ target.addEventListener(event, handler);
21
+ activeListeners.push({ target, event, handler });
22
+ }
23
+ // ─── Dropdown Toggling ───────────────────────────────────────────────────────
24
+ function initDropdowns() {
25
+ const handler = (e) => {
26
+ const target = e.target;
27
+ const trigger = target.closest('[data-ux4g-dropdown-toggle]');
28
+ if (trigger) {
29
+ e.preventDefault();
30
+ const dropdown = trigger.closest('.ux4g-dropdown');
31
+ if (dropdown) {
32
+ const isOpen = dropdown.classList.contains('is-open');
33
+ // Close all other open dropdowns
34
+ document.querySelectorAll('.ux4g-dropdown.is-open').forEach((el) => {
35
+ if (el !== dropdown)
36
+ el.classList.remove('is-open');
37
+ });
38
+ dropdown.classList.toggle('is-open', !isOpen);
39
+ }
40
+ return;
41
+ }
42
+ // Close dropdowns when clicking outside
43
+ if (!target.closest('.ux4g-dropdown')) {
44
+ document.querySelectorAll('.ux4g-dropdown.is-open').forEach((el) => {
45
+ el.classList.remove('is-open');
46
+ });
47
+ }
48
+ };
49
+ addListener(document, 'click', handler);
50
+ }
51
+ // ─── Modal Management ────────────────────────────────────────────────────────
52
+ function initModals() {
53
+ const openHandler = (e) => {
54
+ const target = e.target;
55
+ const trigger = target.closest('[data-ux4g-modal-open]');
56
+ if (trigger) {
57
+ e.preventDefault();
58
+ const modalId = trigger.getAttribute('data-ux4g-modal-open');
59
+ if (modalId) {
60
+ const backdrop = document.getElementById(modalId);
61
+ if (backdrop) {
62
+ backdrop.classList.add('is-open');
63
+ document.body.style.overflow = 'hidden';
64
+ }
65
+ }
66
+ }
67
+ };
68
+ const closeHandler = (e) => {
69
+ const target = e.target;
70
+ // Close button inside modal
71
+ const closeBtn = target.closest('[data-ux4g-modal-close]');
72
+ if (closeBtn) {
73
+ e.preventDefault();
74
+ const backdrop = closeBtn.closest('.ux4g-modal-backdrop');
75
+ if (backdrop) {
76
+ backdrop.classList.remove('is-open');
77
+ document.body.style.overflow = '';
78
+ }
79
+ return;
80
+ }
81
+ // Click on backdrop (outside modal box) closes modal
82
+ if (target.classList.contains('ux4g-modal-backdrop') && target.classList.contains('is-open')) {
83
+ target.classList.remove('is-open');
84
+ document.body.style.overflow = '';
85
+ }
86
+ };
87
+ const keyHandler = (e) => {
88
+ if (e.key === 'Escape') {
89
+ const openModal = document.querySelector('.ux4g-modal-backdrop.is-open');
90
+ if (openModal) {
91
+ openModal.classList.remove('is-open');
92
+ document.body.style.overflow = '';
93
+ }
94
+ }
95
+ };
96
+ addListener(document, 'click', openHandler);
97
+ addListener(document, 'click', closeHandler);
98
+ addListener(document, 'keydown', keyHandler);
99
+ }
100
+ // ─── Tooltip Positioning ─────────────────────────────────────────────────────
101
+ function initTooltips() {
102
+ const showHandler = (e) => {
103
+ const target = e.target;
104
+ const trigger = target.closest('[data-ux4g-tooltip]');
105
+ if (trigger) {
106
+ const tooltipId = trigger.getAttribute('data-ux4g-tooltip');
107
+ if (tooltipId) {
108
+ const tooltip = document.getElementById(tooltipId);
109
+ if (tooltip) {
110
+ tooltip.classList.add('show');
111
+ }
112
+ }
113
+ }
114
+ };
115
+ const hideHandler = (e) => {
116
+ const target = e.target;
117
+ const trigger = target.closest('[data-ux4g-tooltip]');
118
+ if (trigger) {
119
+ const tooltipId = trigger.getAttribute('data-ux4g-tooltip');
120
+ if (tooltipId) {
121
+ const tooltip = document.getElementById(tooltipId);
122
+ if (tooltip) {
123
+ tooltip.classList.remove('show');
124
+ }
125
+ }
126
+ }
127
+ };
128
+ addListener(document, 'mouseenter', showHandler);
129
+ addListener(document, 'mouseleave', hideHandler);
130
+ addListener(document, 'focusin', showHandler);
131
+ addListener(document, 'focusout', hideHandler);
132
+ }
133
+ // ─── Accordion Expand/Collapse ───────────────────────────────────────────────
134
+ function initAccordions() {
135
+ const handler = (e) => {
136
+ const target = e.target;
137
+ const trigger = target.closest('[data-ux4g-accordion-toggle]');
138
+ if (trigger) {
139
+ e.preventDefault();
140
+ const item = trigger.closest('.ux4g-accordion-item');
141
+ if (item) {
142
+ const isExpanded = item.classList.contains('is-expanded');
143
+ item.classList.toggle('is-expanded', !isExpanded);
144
+ // Update aria-expanded attribute
145
+ trigger.setAttribute('aria-expanded', String(!isExpanded));
146
+ // Toggle the panel visibility
147
+ const panel = item.querySelector('.ux4g-accordion-panel');
148
+ if (panel) {
149
+ panel.style.display = isExpanded ? 'none' : '';
150
+ }
151
+ }
152
+ }
153
+ };
154
+ addListener(document, 'click', handler);
155
+ }
156
+ // ─── Carousel Sliding ────────────────────────────────────────────────────────
157
+ function initCarousels() {
158
+ const handler = (e) => {
159
+ const target = e.target;
160
+ // Next button
161
+ const nextBtn = target.closest('[data-ux4g-carousel-next]');
162
+ if (nextBtn) {
163
+ e.preventDefault();
164
+ const carousel = nextBtn.closest('.ux4g-carousel');
165
+ if (carousel) {
166
+ const items = carousel.querySelectorAll('.ux4g-carousel-item');
167
+ const activeItem = carousel.querySelector('.ux4g-carousel-item.active');
168
+ if (activeItem && items.length > 0) {
169
+ const currentIndex = Array.from(items).indexOf(activeItem);
170
+ const nextIndex = (currentIndex + 1) % items.length;
171
+ activeItem.classList.remove('active');
172
+ items[nextIndex].classList.add('active');
173
+ }
174
+ }
175
+ return;
176
+ }
177
+ // Previous button
178
+ const prevBtn = target.closest('[data-ux4g-carousel-prev]');
179
+ if (prevBtn) {
180
+ e.preventDefault();
181
+ const carousel = prevBtn.closest('.ux4g-carousel');
182
+ if (carousel) {
183
+ const items = carousel.querySelectorAll('.ux4g-carousel-item');
184
+ const activeItem = carousel.querySelector('.ux4g-carousel-item.active');
185
+ if (activeItem && items.length > 0) {
186
+ const currentIndex = Array.from(items).indexOf(activeItem);
187
+ const prevIndex = (currentIndex - 1 + items.length) % items.length;
188
+ activeItem.classList.remove('active');
189
+ items[prevIndex].classList.add('active');
190
+ }
191
+ }
192
+ }
193
+ };
194
+ addListener(document, 'click', handler);
195
+ }
196
+ // ─── Drawer Open/Close ───────────────────────────────────────────────────────
197
+ function initDrawers() {
198
+ const openHandler = (e) => {
199
+ const target = e.target;
200
+ const trigger = target.closest('[data-ux4g-drawer-open]');
201
+ if (trigger) {
202
+ e.preventDefault();
203
+ const drawerId = trigger.getAttribute('data-ux4g-drawer-open');
204
+ if (drawerId) {
205
+ const drawer = document.getElementById(drawerId);
206
+ if (drawer) {
207
+ drawer.classList.add('ux4g-drawer-open');
208
+ document.body.style.overflow = 'hidden';
209
+ }
210
+ }
211
+ }
212
+ };
213
+ const closeHandler = (e) => {
214
+ const target = e.target;
215
+ const closeBtn = target.closest('[data-ux4g-drawer-close]');
216
+ if (closeBtn) {
217
+ e.preventDefault();
218
+ const drawer = closeBtn.closest('.ux4g-drawer');
219
+ if (drawer) {
220
+ drawer.classList.remove('ux4g-drawer-open');
221
+ document.body.style.overflow = '';
222
+ }
223
+ }
224
+ };
225
+ const keyHandler = (e) => {
226
+ if (e.key === 'Escape') {
227
+ const openDrawer = document.querySelector('.ux4g-drawer.ux4g-drawer-open');
228
+ if (openDrawer) {
229
+ openDrawer.classList.remove('ux4g-drawer-open');
230
+ document.body.style.overflow = '';
231
+ }
232
+ }
233
+ };
234
+ addListener(document, 'click', openHandler);
235
+ addListener(document, 'click', closeHandler);
236
+ addListener(document, 'keydown', keyHandler);
237
+ }
238
+ // ─── Public API ──────────────────────────────────────────────────────────────
239
+ /**
240
+ * Initialize the UX4G runtime.
241
+ *
242
+ * Encapsulates all interactive behaviors from the CDN scripts (ux4g.js, ux4g-custom.js):
243
+ * - Dropdown toggling
244
+ * - Modal management (open/close/escape/backdrop click)
245
+ * - Tooltip positioning (show/hide on hover/focus)
246
+ * - Accordion expand/collapse
247
+ * - Carousel sliding (next/prev)
248
+ * - Drawer open/close
249
+ *
250
+ * Uses a singleton guard to ensure initialization happens exactly once.
251
+ * Safe for SSR — no-ops when window/document are unavailable.
252
+ */
253
+ function initRuntime() {
254
+ // Guard against non-browser environments (SSR/Node.js)
255
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
256
+ return;
257
+ }
258
+ // Singleton guard: prevent duplicate initialization
259
+ if (window.__UX4G_RUNTIME_INITIALIZED__) {
260
+ return;
261
+ }
262
+ // Initialize all interactive behaviors
263
+ initDropdowns();
264
+ initModals();
265
+ initTooltips();
266
+ initAccordions();
267
+ initCarousels();
268
+ initDrawers();
269
+ // Mark as initialized
270
+ window.__UX4G_RUNTIME_INITIALIZED__ = true;
271
+ }
272
+ /**
273
+ * Destroy the UX4G runtime.
274
+ *
275
+ * Resets the singleton guard and removes all event listeners.
276
+ * Useful for testing and SSR cleanup.
277
+ */
278
+ function destroyRuntime() {
279
+ // Guard against non-browser environments
280
+ if (typeof window === 'undefined') {
281
+ return;
282
+ }
283
+ // Remove all registered event listeners
284
+ for (const { target, event, handler } of activeListeners) {
285
+ target.removeEventListener(event, handler);
286
+ }
287
+ activeListeners = [];
288
+ // Disconnect all observers
289
+ for (const observer of activeObservers) {
290
+ observer.disconnect();
291
+ }
292
+ activeObservers = [];
293
+ // Reset the singleton guard
294
+ window.__UX4G_RUNTIME_INITIALIZED__ = undefined;
295
+ }
296
+
297
+ export { destroyRuntime, initRuntime };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ux4g-components-web",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "UX4G Design System — CSS bundle, design tokens, utilities, and shared Class_Builder types",
6
6
  "scripts": {
@@ -19,19 +19,38 @@
19
19
  "import": "./dist/types/index.mjs",
20
20
  "require": "./dist/types/index.cjs",
21
21
  "default": "./dist/types/index.mjs"
22
+ },
23
+ "./runtime": {
24
+ "types": "./dist/runtime/index.d.ts",
25
+ "import": "./dist/runtime/index.mjs",
26
+ "require": "./dist/runtime/index.cjs",
27
+ "default": "./dist/runtime/index.mjs"
28
+ },
29
+ "./runtime/bootstrap": {
30
+ "types": "./dist/runtime/bootstrap.d.ts",
31
+ "import": "./dist/runtime/bootstrap.mjs",
32
+ "require": "./dist/runtime/bootstrap.cjs",
33
+ "default": "./dist/runtime/bootstrap.mjs"
22
34
  }
23
35
  },
24
36
  "typesVersions": {
25
37
  "*": {
26
38
  "types": [
27
39
  "./dist/types/index.d.ts"
40
+ ],
41
+ "runtime": [
42
+ "./dist/runtime/index.d.ts"
43
+ ],
44
+ "runtime/bootstrap": [
45
+ "./dist/runtime/bootstrap.d.ts"
28
46
  ]
29
47
  }
30
48
  },
31
49
  "files": [
32
50
  "styles/ux4g.css",
33
51
  "styles/cascade-fixes.css",
34
- "dist/"
52
+ "dist/",
53
+ "dist/runtime/"
35
54
  ],
36
55
  "devDependencies": {
37
56
  "@rollup/plugin-typescript": "^12.0.0",