juxscript 1.0.3 → 1.0.5

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 (73) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/barchart.ts +1248 -0
  7. package/lib/components/button.ts +188 -53
  8. package/lib/components/card.ts +75 -61
  9. package/lib/components/chart.ts +17 -15
  10. package/lib/components/checkbox.ts +199 -0
  11. package/lib/components/code.ts +66 -152
  12. package/lib/components/container.ts +104 -208
  13. package/lib/components/data.ts +1 -3
  14. package/lib/components/datepicker.ts +226 -0
  15. package/lib/components/dialog.ts +258 -0
  16. package/lib/components/docs-data.json +1969 -423
  17. package/lib/components/dropdown.ts +244 -0
  18. package/lib/components/element.ts +271 -0
  19. package/lib/components/fileupload.ts +319 -0
  20. package/lib/components/footer.ts +37 -18
  21. package/lib/components/header.ts +53 -33
  22. package/lib/components/heading.ts +119 -0
  23. package/lib/components/helpers.ts +34 -0
  24. package/lib/components/hero.ts +57 -31
  25. package/lib/components/include.ts +292 -0
  26. package/lib/components/input.ts +508 -77
  27. package/lib/components/layout.ts +144 -18
  28. package/lib/components/list.ts +83 -74
  29. package/lib/components/loading.ts +263 -0
  30. package/lib/components/main.ts +43 -17
  31. package/lib/components/menu.ts +108 -24
  32. package/lib/components/modal.ts +50 -21
  33. package/lib/components/nav.ts +60 -18
  34. package/lib/components/paragraph.ts +111 -0
  35. package/lib/components/progress.ts +276 -0
  36. package/lib/components/radio.ts +236 -0
  37. package/lib/components/req.ts +300 -0
  38. package/lib/components/script.ts +33 -74
  39. package/lib/components/select.ts +280 -0
  40. package/lib/components/sidebar.ts +87 -37
  41. package/lib/components/style.ts +47 -70
  42. package/lib/components/switch.ts +261 -0
  43. package/lib/components/table.ts +47 -24
  44. package/lib/components/tabs.ts +105 -63
  45. package/lib/components/theme-toggle.ts +361 -0
  46. package/lib/components/token-calculator.ts +380 -0
  47. package/lib/components/tooltip.ts +244 -0
  48. package/lib/components/view.ts +36 -20
  49. package/lib/components/write.ts +284 -0
  50. package/lib/globals.d.ts +21 -0
  51. package/lib/jux.ts +178 -68
  52. package/lib/presets/notion.css +521 -0
  53. package/lib/presets/notion.jux +27 -0
  54. package/lib/reactivity/state.ts +364 -0
  55. package/lib/themes/charts.js +126 -0
  56. package/machinery/compiler.js +126 -38
  57. package/machinery/generators/html.js +2 -3
  58. package/machinery/server.js +2 -2
  59. package/package.json +29 -3
  60. package/lib/components/import.ts +0 -430
  61. package/lib/components/node.ts +0 -200
  62. package/lib/components/reactivity.js +0 -104
  63. package/lib/components/theme.ts +0 -97
  64. package/lib/layouts/notion.css +0 -258
  65. package/lib/styles/base-theme.css +0 -186
  66. package/lib/styles/dark-theme.css +0 -144
  67. package/lib/styles/light-theme.css +0 -144
  68. package/lib/styles/tokens/dark.css +0 -86
  69. package/lib/styles/tokens/light.css +0 -86
  70. package/lib/templates/index.juxt +0 -33
  71. package/lib/themes/dark.css +0 -86
  72. package/lib/themes/light.css +0 -86
  73. /package/lib/{styles → presets}/global.css +0 -0
@@ -1,4 +1,4 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
2
 
3
3
  /**
4
4
  * Modal component options
@@ -9,6 +9,8 @@ export interface ModalOptions {
9
9
  showCloseButton?: boolean;
10
10
  closeOnBackdropClick?: boolean;
11
11
  size?: 'small' | 'medium' | 'large';
12
+ style?: string;
13
+ class?: string;
12
14
  }
13
15
 
14
16
  /**
@@ -21,6 +23,8 @@ type ModalState = {
21
23
  closeOnBackdropClick: boolean;
22
24
  size: string;
23
25
  isOpen: boolean;
26
+ style: string;
27
+ class: string;
24
28
  };
25
29
 
26
30
  /**
@@ -35,22 +39,26 @@ type ModalState = {
35
39
  * modal.render();
36
40
  * modal.open();
37
41
  */
38
- export class Modal extends Reactive {
39
- state!: ModalState;
42
+ export class Modal {
43
+ state: ModalState;
40
44
  container: HTMLElement | null = null;
45
+ _id: string;
46
+ id: string;
41
47
 
42
- constructor(componentId: string, options: ModalOptions = {}) {
43
- super();
44
- this._setComponentId(componentId);
48
+ constructor(id: string, options: ModalOptions = {}) {
49
+ this._id = id;
50
+ this.id = id;
45
51
 
46
- this.state = this._createReactiveState({
52
+ this.state = {
47
53
  title: options.title ?? '',
48
54
  content: options.content ?? '',
49
55
  showCloseButton: options.showCloseButton ?? true,
50
56
  closeOnBackdropClick: options.closeOnBackdropClick ?? true,
51
57
  size: options.size ?? 'medium',
52
- isOpen: false
53
- }) as ModalState;
58
+ isOpen: false,
59
+ style: options.style ?? '',
60
+ class: options.class ?? ''
61
+ };
54
62
  }
55
63
 
56
64
  /* -------------------------
@@ -82,6 +90,20 @@ export class Modal extends Reactive {
82
90
  return this;
83
91
  }
84
92
 
93
+ style(value: string): this {
94
+ this.state.style = value;
95
+ return this;
96
+ }
97
+
98
+ class(value: string): this {
99
+ this.state.class = value;
100
+ return this;
101
+ }
102
+
103
+ /* -------------------------
104
+ * Modal controls
105
+ * ------------------------- */
106
+
85
107
  open(): this {
86
108
  this.state.isOpen = true;
87
109
  if (this.container) {
@@ -90,7 +112,6 @@ export class Modal extends Reactive {
90
112
  modalEl.classList.add('jux-modal-open');
91
113
  }
92
114
  }
93
- this.emit('open');
94
115
  return this;
95
116
  }
96
117
 
@@ -102,7 +123,6 @@ export class Modal extends Reactive {
102
123
  modalEl.classList.remove('jux-modal-open');
103
124
  }
104
125
  }
105
- this.emit('close');
106
126
  return this;
107
127
  }
108
128
 
@@ -120,16 +140,24 @@ export class Modal extends Reactive {
120
140
  }
121
141
  container = target;
122
142
  } else {
123
- container = getOrCreateContainer(this._componentId) as HTMLElement;
143
+ container = getOrCreateContainer(this._id);
124
144
  }
125
145
 
126
146
  this.container = container;
127
- const { title, content, showCloseButton, closeOnBackdropClick, size, isOpen } = this.state;
147
+ const { title, content, showCloseButton, closeOnBackdropClick, size, isOpen, style, class: className } = this.state;
128
148
 
129
149
  // Modal backdrop
130
150
  const modal = document.createElement('div');
131
151
  modal.className = `jux-modal jux-modal-${size}`;
132
- modal.id = this._componentId;
152
+ modal.id = this._id;
153
+
154
+ if (className) {
155
+ modal.className += ` ${className}`;
156
+ }
157
+
158
+ if (style) {
159
+ modal.setAttribute('style', style);
160
+ }
133
161
 
134
162
  if (isOpen) {
135
163
  modal.classList.add('jux-modal-open');
@@ -183,11 +211,12 @@ export class Modal extends Reactive {
183
211
  }
184
212
 
185
213
  // Event binding - escape key
186
- document.addEventListener('keydown', (e) => {
214
+ const escapeHandler = (e: KeyboardEvent) => {
187
215
  if (e.key === 'Escape' && this.state.isOpen) {
188
216
  this.close();
189
217
  }
190
- });
218
+ };
219
+ document.addEventListener('keydown', escapeHandler);
191
220
 
192
221
  return this;
193
222
  }
@@ -200,17 +229,17 @@ export class Modal extends Reactive {
200
229
  throw new Error('Modal.renderTo: Invalid component - not an object');
201
230
  }
202
231
 
203
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
204
- throw new Error('Modal.renderTo: Invalid component - missing _componentId (not a Jux component)');
232
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
233
+ throw new Error('Modal.renderTo: Invalid component - missing _id (not a Jux component)');
205
234
  }
206
235
 
207
- return this.render(`#${juxComponent._componentId}`);
236
+ return this.render(`#${juxComponent._id}`);
208
237
  }
209
238
  }
210
239
 
211
240
  /**
212
241
  * Factory helper
213
242
  */
214
- export function modal(componentId: string, options: ModalOptions = {}): Modal {
215
- return new Modal(componentId, options);
243
+ export function modal(id: string, options: ModalOptions = {}): Modal {
244
+ return new Modal(id, options);
216
245
  }
@@ -1,4 +1,5 @@
1
- import { Reactive, getOrCreateContainer } from './reactivity.js';
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { req } from './req.js';
2
3
 
3
4
  /**
4
5
  * Nav item configuration
@@ -15,6 +16,8 @@ export interface NavItem {
15
16
  export interface NavOptions {
16
17
  items?: NavItem[];
17
18
  variant?: 'default' | 'pills' | 'tabs';
19
+ style?: string;
20
+ class?: string;
18
21
  }
19
22
 
20
23
  /**
@@ -23,6 +26,8 @@ export interface NavOptions {
23
26
  type NavState = {
24
27
  items: NavItem[];
25
28
  variant: string;
29
+ style: string;
30
+ class: string;
26
31
  };
27
32
 
28
33
  /**
@@ -38,18 +43,35 @@ type NavState = {
38
43
  * });
39
44
  * nav.render();
40
45
  */
41
- export class Nav extends Reactive {
42
- state!: NavState;
46
+ export class Nav {
47
+ state: NavState;
43
48
  container: HTMLElement | null = null;
49
+ _id: string;
50
+ id: string;
44
51
 
45
- constructor(componentId: string, options: NavOptions = {}) {
46
- super();
47
- this._setComponentId(componentId);
52
+ constructor(id: string, options: NavOptions = {}) {
53
+ this._id = id;
54
+ this.id = id;
48
55
 
49
- this.state = this._createReactiveState({
56
+ this.state = {
50
57
  items: options.items ?? [],
51
- variant: options.variant ?? 'default'
52
- }) as NavState;
58
+ variant: options.variant ?? 'default',
59
+ style: options.style ?? '',
60
+ class: options.class ?? ''
61
+ };
62
+
63
+ // Auto-set active state based on current path
64
+ this._setActiveStates();
65
+ }
66
+
67
+ /**
68
+ * Set active state on items based on current request path
69
+ */
70
+ private _setActiveStates(): void {
71
+ this.state.items = this.state.items.map(item => ({
72
+ ...item,
73
+ active: req.isActiveNavItem(item.href)
74
+ }));
53
75
  }
54
76
 
55
77
  /* -------------------------
@@ -58,11 +80,13 @@ export class Nav extends Reactive {
58
80
 
59
81
  items(value: NavItem[]): this {
60
82
  this.state.items = value;
83
+ this._setActiveStates();
61
84
  return this;
62
85
  }
63
86
 
64
87
  addItem(item: NavItem): this {
65
- this.state.items.push(item);
88
+ this.state.items = [...this.state.items, item];
89
+ this._setActiveStates();
66
90
  return this;
67
91
  }
68
92
 
@@ -71,6 +95,16 @@ export class Nav extends Reactive {
71
95
  return this;
72
96
  }
73
97
 
98
+ style(value: string): this {
99
+ this.state.style = value;
100
+ return this;
101
+ }
102
+
103
+ class(value: string): this {
104
+ this.state.class = value;
105
+ return this;
106
+ }
107
+
74
108
  /* -------------------------
75
109
  * Render
76
110
  * ------------------------- */
@@ -85,15 +119,23 @@ export class Nav extends Reactive {
85
119
  }
86
120
  container = target;
87
121
  } else {
88
- container = getOrCreateContainer(this._componentId) as HTMLElement;
122
+ container = getOrCreateContainer(this._id);
89
123
  }
90
124
 
91
125
  this.container = container;
92
- const { items, variant } = this.state;
126
+ const { items, variant, style, class: className } = this.state;
93
127
 
94
128
  const nav = document.createElement('nav');
95
129
  nav.className = `jux-nav jux-nav-${variant}`;
96
- nav.id = this._componentId;
130
+ nav.id = this._id;
131
+
132
+ if (className) {
133
+ nav.className += ` ${className}`;
134
+ }
135
+
136
+ if (style) {
137
+ nav.setAttribute('style', style);
138
+ }
97
139
 
98
140
  items.forEach(item => {
99
141
  const link = document.createElement('a');
@@ -120,17 +162,17 @@ export class Nav extends Reactive {
120
162
  throw new Error('Nav.renderTo: Invalid component - not an object');
121
163
  }
122
164
 
123
- if (!juxComponent._componentId || typeof juxComponent._componentId !== 'string') {
124
- throw new Error('Nav.renderTo: Invalid component - missing _componentId (not a Jux component)');
165
+ if (!juxComponent._id || typeof juxComponent._id !== 'string') {
166
+ throw new Error('Nav.renderTo: Invalid component - missing _id (not a Jux component)');
125
167
  }
126
168
 
127
- return this.render(`#${juxComponent._componentId}`);
169
+ return this.render(`#${juxComponent._id}`);
128
170
  }
129
171
  }
130
172
 
131
173
  /**
132
174
  * Factory helper
133
175
  */
134
- export function nav(componentId: string, options: NavOptions = {}): Nav {
135
- return new Nav(componentId, options);
176
+ export function nav(id: string, options: NavOptions = {}): Nav {
177
+ return new Nav(id, options);
136
178
  }
@@ -0,0 +1,111 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+
3
+ /**
4
+ * Paragraph options
5
+ */
6
+ export interface ParagraphOptions {
7
+ text?: string;
8
+ class?: string;
9
+ style?: string;
10
+ }
11
+
12
+ /**
13
+ * Paragraph state
14
+ */
15
+ type ParagraphState = {
16
+ text: string;
17
+ class: string;
18
+ style: string;
19
+ };
20
+
21
+ /**
22
+ * Paragraph component - semantic paragraph element
23
+ *
24
+ * Usage:
25
+ * jux.paragraph('intro', { text: 'Welcome to JUX' }).render('#app');
26
+ * jux.paragraph('description').text('A simple framework').render('#app');
27
+ */
28
+ export class Paragraph {
29
+ state: ParagraphState;
30
+ container: HTMLElement | null = null;
31
+ _id: string;
32
+ id: string;
33
+
34
+ constructor(id: string, options: ParagraphOptions = {}) {
35
+ this._id = id;
36
+ this.id = id;
37
+
38
+ this.state = {
39
+ text: options.text ?? '',
40
+ class: options.class ?? '',
41
+ style: options.style ?? ''
42
+ };
43
+ }
44
+
45
+ /* -------------------------
46
+ * Fluent API
47
+ * ------------------------- */
48
+
49
+ text(value: string): this {
50
+ this.state.text = value;
51
+ return this;
52
+ }
53
+
54
+ class(value: string): this {
55
+ this.state.class = value;
56
+ return this;
57
+ }
58
+
59
+ style(value: string): this {
60
+ this.state.style = value;
61
+ return this;
62
+ }
63
+
64
+ /* -------------------------
65
+ * Render
66
+ * ------------------------- */
67
+
68
+ render(targetId?: string | HTMLElement): this {
69
+ let container: HTMLElement;
70
+
71
+ if (targetId) {
72
+ if (targetId instanceof HTMLElement) {
73
+ container = targetId;
74
+ } else {
75
+ const target = document.querySelector(targetId);
76
+ if (!target || !(target instanceof HTMLElement)) {
77
+ throw new Error(`Paragraph: Target element "${targetId}" not found`);
78
+ }
79
+ container = target;
80
+ }
81
+ } else {
82
+ container = getOrCreateContainer(this._id);
83
+ }
84
+
85
+ this.container = container;
86
+ const { text, class: className, style } = this.state;
87
+
88
+ const p = document.createElement('p');
89
+ p.id = this._id;
90
+ p.textContent = text;
91
+
92
+ if (className) {
93
+ p.className = className;
94
+ }
95
+
96
+ if (style) {
97
+ p.setAttribute('style', style);
98
+ }
99
+
100
+ container.appendChild(p);
101
+
102
+ return this;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Factory helper
108
+ */
109
+ export function paragraph(id: string, options: ParagraphOptions = {}): Paragraph {
110
+ return new Paragraph(id, options);
111
+ }
@@ -0,0 +1,276 @@
1
+ import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
3
+
4
+ /**
5
+ * Progress component options
6
+ */
7
+ export interface ProgressOptions {
8
+ value?: number;
9
+ max?: number;
10
+ label?: string;
11
+ showPercentage?: boolean;
12
+ variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
13
+ size?: 'sm' | 'md' | 'lg';
14
+ striped?: boolean;
15
+ animated?: boolean;
16
+ style?: string;
17
+ class?: string;
18
+ }
19
+
20
+ /**
21
+ * Progress component state
22
+ */
23
+ type ProgressState = {
24
+ value: number;
25
+ max: number;
26
+ label: string;
27
+ showPercentage: boolean;
28
+ variant: string;
29
+ size: string;
30
+ striped: boolean;
31
+ animated: boolean;
32
+ style: string;
33
+ class: string;
34
+ };
35
+
36
+ /**
37
+ * Progress component - Progress bar for loading/completion
38
+ *
39
+ * Usage:
40
+ * jux.progress('upload', {
41
+ * value: 45,
42
+ * max: 100,
43
+ * label: 'Uploading...',
44
+ * showPercentage: true,
45
+ * animated: true
46
+ * }).render('#app');
47
+ *
48
+ * // Update progress
49
+ * const prog = jux.progress('upload').render('#app');
50
+ * prog.value(75);
51
+ */
52
+ export class Progress {
53
+ state: ProgressState;
54
+ container: HTMLElement | null = null;
55
+ _id: string;
56
+ id: string;
57
+ private _boundState?: State<number>;
58
+
59
+ constructor(id: string, options: ProgressOptions = {}) {
60
+ this._id = id;
61
+ this.id = id;
62
+
63
+ this.state = {
64
+ value: options.value ?? 0,
65
+ max: options.max ?? 100,
66
+ label: options.label ?? '',
67
+ showPercentage: options.showPercentage ?? false,
68
+ variant: options.variant ?? 'default',
69
+ size: options.size ?? 'md',
70
+ striped: options.striped ?? false,
71
+ animated: options.animated ?? false,
72
+ style: options.style ?? '',
73
+ class: options.class ?? ''
74
+ };
75
+ }
76
+
77
+ /* -------------------------
78
+ * Fluent API
79
+ * ------------------------- */
80
+
81
+ value(value: number): this {
82
+ this.state.value = Math.max(0, Math.min(value, this.state.max));
83
+ this._updateElement();
84
+ return this;
85
+ }
86
+
87
+ max(value: number): this {
88
+ this.state.max = value;
89
+ return this;
90
+ }
91
+
92
+ label(value: string): this {
93
+ this.state.label = value;
94
+ this._updateElement();
95
+ return this;
96
+ }
97
+
98
+ showPercentage(value: boolean): this {
99
+ this.state.showPercentage = value;
100
+ this._updateElement();
101
+ return this;
102
+ }
103
+
104
+ variant(value: 'default' | 'success' | 'warning' | 'error' | 'info'): this {
105
+ this.state.variant = value;
106
+ this._updateElement();
107
+ return this;
108
+ }
109
+
110
+ size(value: 'sm' | 'md' | 'lg'): this {
111
+ this.state.size = value;
112
+ return this;
113
+ }
114
+
115
+ striped(value: boolean): this {
116
+ this.state.striped = value;
117
+ this._updateElement();
118
+ return this;
119
+ }
120
+
121
+ animated(value: boolean): this {
122
+ this.state.animated = value;
123
+ this._updateElement();
124
+ return this;
125
+ }
126
+
127
+ style(value: string): this {
128
+ this.state.style = value;
129
+ return this;
130
+ }
131
+
132
+ class(value: string): this {
133
+ this.state.class = value;
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Two-way binding to state
139
+ */
140
+ bind(stateObj: State<number>): this {
141
+ this._boundState = stateObj;
142
+
143
+ stateObj.subscribe((val) => {
144
+ this.value(val);
145
+ });
146
+
147
+ return this;
148
+ }
149
+
150
+ /* -------------------------
151
+ * Helpers
152
+ * ------------------------- */
153
+
154
+ private _updateElement(): void {
155
+ const wrapper = document.getElementById(this._id);
156
+ const bar = document.getElementById(`${this._id}-bar`);
157
+ const labelEl = document.getElementById(`${this._id}-label`);
158
+
159
+ // If element has a value attribute set externally (e.g., by bindValue), sync state
160
+ if (wrapper && wrapper.hasAttribute('data-value')) {
161
+ const externalValue = parseFloat(wrapper.getAttribute('data-value') || '0');
162
+ this.state.value = externalValue;
163
+ }
164
+
165
+ if (bar) {
166
+ const percentage = (this.state.value / this.state.max) * 100;
167
+ bar.style.width = `${percentage}%`;
168
+ bar.setAttribute('aria-valuenow', this.state.value.toString());
169
+ bar.className = `jux-progress-bar jux-progress-bar-${this.state.variant}`;
170
+
171
+ if (this.state.striped) {
172
+ bar.classList.add('jux-progress-bar-striped');
173
+ }
174
+ if (this.state.animated) {
175
+ bar.classList.add('jux-progress-bar-animated');
176
+ }
177
+ }
178
+
179
+ if (labelEl) {
180
+ const percentage = Math.round((this.state.value / this.state.max) * 100);
181
+ const text = this.state.showPercentage
182
+ ? `${this.state.label} ${percentage}%`.trim()
183
+ : this.state.label;
184
+ labelEl.textContent = text;
185
+ }
186
+ }
187
+
188
+ getPercentage(): number {
189
+ return Math.round((this.state.value / this.state.max) * 100);
190
+ }
191
+
192
+ /* -------------------------
193
+ * Render
194
+ * ------------------------- */
195
+
196
+ render(targetId?: string): this {
197
+ let container: HTMLElement;
198
+
199
+ if (targetId) {
200
+ const target = document.querySelector(targetId);
201
+ if (!target || !(target instanceof HTMLElement)) {
202
+ throw new Error(`Progress: Target element "${targetId}" not found`);
203
+ }
204
+ container = target;
205
+ } else {
206
+ container = getOrCreateContainer(this._id);
207
+ }
208
+
209
+ this.container = container;
210
+ const { value, max, label, showPercentage, variant, size, striped, animated, style, class: className } = this.state;
211
+
212
+ const wrapper = document.createElement('div');
213
+ wrapper.className = `jux-progress jux-progress-${size}`;
214
+ wrapper.id = this._id;
215
+
216
+ if (className) {
217
+ wrapper.className += ` ${className}`;
218
+ }
219
+
220
+ if (style) {
221
+ wrapper.setAttribute('style', style);
222
+ }
223
+
224
+ // Label
225
+ if (label || showPercentage) {
226
+ const labelEl = document.createElement('div');
227
+ labelEl.className = 'jux-progress-label';
228
+ labelEl.id = `${this._id}-label`;
229
+ const percentage = Math.round((value / max) * 100);
230
+ const text = showPercentage
231
+ ? `${label} ${percentage}%`.trim()
232
+ : label;
233
+ labelEl.textContent = text;
234
+ wrapper.appendChild(labelEl);
235
+ }
236
+
237
+ // Progress track
238
+ const track = document.createElement('div');
239
+ track.className = 'jux-progress-track';
240
+
241
+ // Progress bar
242
+ const bar = document.createElement('div');
243
+ bar.className = `jux-progress-bar jux-progress-bar-${variant}`;
244
+ bar.id = `${this._id}-bar`;
245
+ bar.setAttribute('role', 'progressbar');
246
+ bar.setAttribute('aria-valuenow', value.toString());
247
+ bar.setAttribute('aria-valuemin', '0');
248
+ bar.setAttribute('aria-valuemax', max.toString());
249
+
250
+ const percentage = (value / max) * 100;
251
+ bar.style.width = `${percentage}%`;
252
+
253
+ if (striped) {
254
+ bar.classList.add('jux-progress-bar-striped');
255
+ }
256
+ if (animated) {
257
+ bar.classList.add('jux-progress-bar-animated');
258
+ }
259
+
260
+ track.appendChild(bar);
261
+ wrapper.appendChild(track);
262
+ container.appendChild(wrapper);
263
+ return this;
264
+ }
265
+
266
+ renderTo(juxComponent: any): this {
267
+ if (!juxComponent?._id) {
268
+ throw new Error('Progress.renderTo: Invalid component');
269
+ }
270
+ return this.render(`#${juxComponent._id}`);
271
+ }
272
+ }
273
+
274
+ export function progress(id: string, options: ProgressOptions = {}): Progress {
275
+ return new Progress(id, options);
276
+ }