juxscript 1.0.19 → 1.0.20

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 (43) hide show
  1. package/lib/components/alert.ts +124 -128
  2. package/lib/components/areachart.ts +169 -287
  3. package/lib/components/areachartsmooth.ts +2 -2
  4. package/lib/components/badge.ts +63 -72
  5. package/lib/components/barchart.ts +120 -48
  6. package/lib/components/button.ts +92 -60
  7. package/lib/components/card.ts +97 -121
  8. package/lib/components/chart-types.ts +159 -0
  9. package/lib/components/chart-utils.ts +160 -0
  10. package/lib/components/chart.ts +628 -48
  11. package/lib/components/checkbox.ts +137 -51
  12. package/lib/components/code.ts +89 -75
  13. package/lib/components/container.ts +1 -1
  14. package/lib/components/datepicker.ts +93 -78
  15. package/lib/components/dialog.ts +163 -130
  16. package/lib/components/divider.ts +111 -193
  17. package/lib/components/docs-data.json +697 -274
  18. package/lib/components/doughnutchart.ts +125 -57
  19. package/lib/components/dropdown.ts +172 -85
  20. package/lib/components/element.ts +66 -61
  21. package/lib/components/fileupload.ts +142 -171
  22. package/lib/components/heading.ts +64 -21
  23. package/lib/components/hero.ts +109 -34
  24. package/lib/components/icon.ts +247 -0
  25. package/lib/components/icons.ts +174 -0
  26. package/lib/components/include.ts +77 -2
  27. package/lib/components/input.ts +105 -53
  28. package/lib/components/list.ts +120 -79
  29. package/lib/components/menu.ts +97 -2
  30. package/lib/components/modal.ts +144 -63
  31. package/lib/components/nav.ts +153 -52
  32. package/lib/components/paragraph.ts +54 -91
  33. package/lib/components/progress.ts +83 -107
  34. package/lib/components/radio.ts +151 -52
  35. package/lib/components/select.ts +110 -102
  36. package/lib/components/sidebar.ts +148 -105
  37. package/lib/components/switch.ts +124 -125
  38. package/lib/components/table.ts +214 -137
  39. package/lib/components/tabs.ts +194 -113
  40. package/lib/components/theme-toggle.ts +38 -7
  41. package/lib/components/tooltip.ts +207 -47
  42. package/lib/jux.ts +24 -5
  43. package/package.json +1 -2
@@ -1,94 +1,68 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
2
3
 
3
- /**
4
- * Card component options
5
- */
6
4
  export interface CardOptions {
7
5
  title?: string;
8
- subtitle?: string;
9
6
  content?: string;
10
- image?: string;
11
- variant?: 'default' | 'elevated' | 'outlined';
7
+ footer?: string;
8
+ variant?: string;
9
+ hoverable?: boolean;
12
10
  style?: string;
13
11
  class?: string;
14
12
  }
15
13
 
16
- /**
17
- * Card component state
18
- */
19
14
  type CardState = {
20
15
  title: string;
21
- subtitle: string;
22
16
  content: string;
23
- image: string;
17
+ footer: string;
24
18
  variant: string;
19
+ hoverable: boolean;
25
20
  style: string;
26
21
  class: string;
27
- hasActions: boolean;
28
22
  };
29
23
 
30
- /**
31
- * Card component
32
- *
33
- * Usage:
34
- * const card = jux.card('myCard', {
35
- * title: 'Card Title',
36
- * content: 'Card content here'
37
- * });
38
- * card.render();
39
- *
40
- * // Add actions (any components)
41
- * jux.button('view-btn').label('View').render('#myCard-actions');
42
- * jux.button('delete-btn').label('Delete').render('#myCard-actions');
43
- */
44
24
  export class Card {
45
25
  state: CardState;
46
26
  container: HTMLElement | null = null;
47
27
  _id: string;
48
28
  id: string;
49
29
 
30
+ // CRITICAL: Store bind/sync instructions for deferred wiring
31
+ private _bindings: Array<{ event: string, handler: Function }> = [];
32
+ private _syncBindings: Array<{
33
+ property: string,
34
+ stateObj: State<any>,
35
+ toState?: Function,
36
+ toComponent?: Function
37
+ }> = [];
38
+
50
39
  constructor(id: string, options: CardOptions = {}) {
51
40
  this._id = id;
52
41
  this.id = id;
53
42
 
54
43
  this.state = {
55
44
  title: options.title ?? '',
56
- subtitle: options.subtitle ?? '',
57
45
  content: options.content ?? '',
58
- image: options.image ?? '',
46
+ footer: options.footer ?? '',
59
47
  variant: options.variant ?? 'default',
48
+ hoverable: options.hoverable ?? false,
60
49
  style: options.style ?? '',
61
- class: options.class ?? '',
62
- hasActions: false
50
+ class: options.class ?? ''
63
51
  };
64
52
  }
65
53
 
66
- /* -------------------------
67
- * Fluent API
68
- * ------------------------- */
69
-
70
54
  title(value: string): this {
71
55
  this.state.title = value;
72
56
  return this;
73
57
  }
74
58
 
75
- subtitle(value: string): this {
76
- this.state.subtitle = value;
77
- return this;
78
- }
79
-
80
59
  content(value: string): this {
81
60
  this.state.content = value;
82
61
  return this;
83
62
  }
84
63
 
85
- image(value: string): this {
86
- this.state.image = value;
87
- return this;
88
- }
89
-
90
- variant(value: string): this {
91
- this.state.variant = value;
64
+ footer(value: string): this {
65
+ this.state.footer = value;
92
66
  return this;
93
67
  }
94
68
 
@@ -102,118 +76,120 @@ export class Card {
102
76
  return this;
103
77
  }
104
78
 
105
- /**
106
- * Enable actions region
107
- * Creates an empty actions container that components can render into
108
- */
109
- withActions(): this {
110
- this.state.hasActions = true;
79
+ bind(event: string, handler: Function): this {
80
+ this._bindings.push({ event, handler });
111
81
  return this;
112
82
  }
113
83
 
114
- /* -------------------------
115
- * Render
116
- * ------------------------- */
84
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
85
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
86
+ throw new Error(`Card.sync: Expected a State object for property "${property}"`);
87
+ }
88
+ this._syncBindings.push({ property, stateObj, toState, toComponent });
89
+ return this;
90
+ }
117
91
 
118
92
  render(targetId?: string): this {
93
+ // === 1. SETUP: Get or create container ===
119
94
  let container: HTMLElement;
120
-
121
95
  if (targetId) {
122
96
  const target = document.querySelector(targetId);
123
97
  if (!target || !(target instanceof HTMLElement)) {
124
- throw new Error(`Card: Target element "${targetId}" not found`);
98
+ throw new Error(`Card: Target "${targetId}" not found`);
125
99
  }
126
100
  container = target;
127
101
  } else {
128
102
  container = getOrCreateContainer(this._id);
129
103
  }
130
-
131
104
  this.container = container;
132
- const { title, subtitle, content, image, variant, style, class: className, hasActions } = this.state;
133
105
 
134
- // Create card element
106
+ // === 2. PREPARE: Destructure state ===
107
+ const { title, content, footer, variant, hoverable, style, class: className } = this.state;
108
+
109
+ // === 3. BUILD: Create DOM elements ===
135
110
  const card = document.createElement('div');
136
111
  card.className = `jux-card jux-card-${variant}`;
137
112
  card.id = this._id;
113
+ if (hoverable) card.classList.add('jux-card-hoverable');
114
+ if (className) card.className += ` ${className}`;
115
+ if (style) card.setAttribute('style', style);
138
116
 
139
- if (className) {
140
- card.className += ` ${className}`;
141
- }
142
-
143
- if (style) {
144
- card.setAttribute('style', style);
145
- }
146
-
147
- // Image
148
- if (image) {
149
- const img = document.createElement('img');
150
- img.className = 'jux-card-image';
151
- img.src = image;
152
- img.alt = title || 'Card image';
153
- card.appendChild(img);
154
- }
155
-
156
- // Content wrapper
157
- const cardBody = document.createElement('div');
158
- cardBody.className = 'jux-card-body';
159
-
160
- // Title
161
117
  if (title) {
162
- const titleEl = document.createElement('h3');
163
- titleEl.className = 'jux-card-title';
164
- titleEl.textContent = title;
165
- cardBody.appendChild(titleEl);
118
+ const header = document.createElement('div');
119
+ header.className = 'jux-card-header';
120
+ header.textContent = title;
121
+ card.appendChild(header);
166
122
  }
167
123
 
168
- // Subtitle
169
- if (subtitle) {
170
- const subtitleEl = document.createElement('p');
171
- subtitleEl.className = 'jux-card-subtitle';
172
- subtitleEl.textContent = subtitle;
173
- cardBody.appendChild(subtitleEl);
174
- }
124
+ const body = document.createElement('div');
125
+ body.className = 'jux-card-body';
126
+ body.innerHTML = content;
127
+ card.appendChild(body);
175
128
 
176
- // Content
177
- if (content) {
178
- const contentEl = document.createElement('p');
179
- contentEl.className = 'jux-card-content';
180
- contentEl.textContent = content;
181
- cardBody.appendChild(contentEl);
129
+ if (footer) {
130
+ const footerEl = document.createElement('div');
131
+ footerEl.className = 'jux-card-footer';
132
+ footerEl.innerHTML = footer;
133
+ card.appendChild(footerEl);
182
134
  }
183
135
 
184
- card.appendChild(cardBody);
185
-
186
- // Actions region (empty container for user to populate)
187
- if (hasActions) {
188
- const actionsEl = document.createElement('div');
189
- actionsEl.className = 'jux-card-actions';
190
- actionsEl.id = `${this._id}-actions`;
191
- card.appendChild(actionsEl);
192
- }
136
+ // === 4. WIRE: Attach event listeners and sync bindings ===
137
+
138
+ // Wire custom bindings from .bind() calls
139
+ this._bindings.forEach(({ event, handler }) => {
140
+ card.addEventListener(event, handler as EventListener);
141
+ });
142
+
143
+ // Wire sync bindings from .sync() calls
144
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
145
+ if (property === 'title') {
146
+ const transformToComponent = toComponent || ((v: any) => String(v));
147
+
148
+ stateObj.subscribe((val: any) => {
149
+ const transformed = transformToComponent(val);
150
+ const header = card.querySelector('.jux-card-header');
151
+ if (header) {
152
+ header.textContent = transformed;
153
+ }
154
+ this.state.title = transformed;
155
+ });
156
+ }
157
+ else if (property === 'content') {
158
+ const transformToComponent = toComponent || ((v: any) => String(v));
159
+
160
+ stateObj.subscribe((val: any) => {
161
+ const transformed = transformToComponent(val);
162
+ body.innerHTML = transformed;
163
+ this.state.content = transformed;
164
+ });
165
+ }
166
+ else if (property === 'footer') {
167
+ const transformToComponent = toComponent || ((v: any) => String(v));
168
+
169
+ stateObj.subscribe((val: any) => {
170
+ const transformed = transformToComponent(val);
171
+ const footerEl = card.querySelector('.jux-card-footer');
172
+ if (footerEl) {
173
+ footerEl.innerHTML = transformed;
174
+ }
175
+ this.state.footer = transformed;
176
+ });
177
+ }
178
+ });
193
179
 
180
+ // === 5. RENDER: Append to DOM and finalize ===
194
181
  container.appendChild(card);
195
182
  return this;
196
183
  }
197
184
 
198
- /**
199
- * Render to another Jux component's container
200
- */
201
185
  renderTo(juxComponent: any): this {
202
- if (!juxComponent || typeof juxComponent !== 'object') {
203
- throw new Error('Card.renderTo: Invalid component - not an object');
186
+ if (!juxComponent?._id) {
187
+ throw new Error('Card.renderTo: Invalid component');
204
188
  }
205
-
206
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
207
- throw new Error('Card.renderTo: Invalid component - missing _id (not a Jux component)');
208
- }
209
-
210
189
  return this.render(`#${juxComponent._id}`);
211
190
  }
212
191
  }
213
192
 
214
- /**
215
- * Factory helper
216
- */
217
193
  export function card(id: string, options: CardOptions = {}): Card {
218
194
  return new Card(id, options);
219
195
  }
@@ -0,0 +1,159 @@
1
+ import { State } from '../reactivity/state.js';
2
+
3
+ /**
4
+ * Shared chart data point
5
+ */
6
+ export interface ChartDataPoint {
7
+ label: string;
8
+ value: number;
9
+ color?: string;
10
+ }
11
+
12
+ /**
13
+ * Shared chart theme type
14
+ */
15
+ export type ChartTheme = 'google' | 'seriesa' | 'hr' | 'figma' | 'notion' | 'chalk' | 'mint';
16
+
17
+ /**
18
+ * Shared chart style mode type
19
+ */
20
+ export type ChartStyleMode = 'default' | 'gradient' | 'outline' | 'dashed' | 'glow' | 'glass';
21
+
22
+ /**
23
+ * Shared chart orientation type
24
+ */
25
+ export type ChartOrientation = 'vertical' | 'horizontal';
26
+
27
+ /**
28
+ * Shared chart direction type
29
+ */
30
+ export type ChartDirection = 'normal' | 'reverse';
31
+
32
+ /**
33
+ * Shared legend orientation type
34
+ */
35
+ export type LegendOrientation = 'horizontal' | 'vertical';
36
+
37
+ /**
38
+ * Base chart options shared across all chart types
39
+ */
40
+ export interface BaseChartOptions {
41
+ data?: ChartDataPoint[];
42
+ title?: string;
43
+ subtitle?: string;
44
+ xAxisLabel?: string;
45
+ yAxisLabel?: string;
46
+ showTicksX?: boolean;
47
+ showTicksY?: boolean;
48
+ showScaleX?: boolean;
49
+ showScaleY?: boolean;
50
+ scaleXUnit?: string;
51
+ scaleYUnit?: string;
52
+ showLegend?: boolean;
53
+ legendOrientation?: LegendOrientation;
54
+ showDataTable?: boolean;
55
+ showDataLabels?: boolean;
56
+ animate?: boolean;
57
+ animationDuration?: number;
58
+ width?: number;
59
+ height?: number;
60
+ colors?: string[];
61
+ class?: string;
62
+ style?: string;
63
+ theme?: ChartTheme;
64
+ styleMode?: ChartStyleMode;
65
+ borderRadius?: number;
66
+ }
67
+
68
+ /**
69
+ * Bar/Area chart specific options (with orientation support)
70
+ */
71
+ export interface BarAreaChartOptions extends BaseChartOptions {
72
+ chartOrientation?: ChartOrientation;
73
+ chartDirection?: ChartDirection;
74
+ }
75
+
76
+ /**
77
+ * Doughnut chart specific options
78
+ */
79
+ export interface DoughnutChartOptions extends BaseChartOptions {
80
+ // Doughnut-specific options can be added here
81
+ }
82
+
83
+ /**
84
+ * Base chart state shared across all chart types
85
+ */
86
+ export interface BaseChartState {
87
+ data: ChartDataPoint[];
88
+ title: string;
89
+ subtitle: string;
90
+ xAxisLabel: string;
91
+ yAxisLabel: string;
92
+ showTicksX: boolean;
93
+ showTicksY: boolean;
94
+ showScaleX: boolean;
95
+ showScaleY: boolean;
96
+ scaleXUnit: string;
97
+ scaleYUnit: string;
98
+ showLegend: boolean;
99
+ legendOrientation: LegendOrientation;
100
+ showDataTable: boolean;
101
+ showDataLabels: boolean;
102
+ animate: boolean;
103
+ animationDuration: number;
104
+ width: number;
105
+ height: number;
106
+ colors: string[];
107
+ class: string;
108
+ style: string;
109
+ theme?: ChartTheme;
110
+ styleMode: ChartStyleMode;
111
+ borderRadius: number;
112
+ }
113
+
114
+ /**
115
+ * Bar/Area chart state with orientation
116
+ */
117
+ export interface BarAreaChartState extends BaseChartState {
118
+ chartOrientation: ChartOrientation;
119
+ chartDirection: ChartDirection;
120
+ }
121
+
122
+ /**
123
+ * Doughnut chart state
124
+ */
125
+ export interface DoughnutChartState extends BaseChartState {
126
+ // Doughnut-specific state can be added here
127
+ }
128
+
129
+ /**
130
+ * Property mapping for syncState
131
+ * Maps state property names to component method names
132
+ */
133
+ export interface ChartPropertyMapping {
134
+ [stateProperty: string]: string | ((value: any) => void);
135
+ }
136
+
137
+ /**
138
+ * Chart state object (all properties as State objects)
139
+ */
140
+ export interface ChartStateObject {
141
+ chartType?: State<string>;
142
+ chartTheme?: State<ChartTheme>;
143
+ chartStyleMode?: State<ChartStyleMode>;
144
+ borderRadius?: State<number>;
145
+ chartTitle?: State<string>;
146
+ chartWidth?: State<number>;
147
+ chartHeight?: State<number>;
148
+ showTicksX?: State<boolean>;
149
+ showTicksY?: State<boolean>;
150
+ showLegend?: State<boolean>;
151
+ showDataTable?: State<boolean>;
152
+ showDataLabels?: State<boolean>;
153
+ animate?: State<boolean>;
154
+ animationDuration?: State<number>;
155
+ legendOrientation?: State<LegendOrientation>;
156
+ chartOrientation?: State<ChartOrientation>;
157
+ chartDirection?: State<ChartDirection>;
158
+ [key: string]: State<any> | undefined;
159
+ }
@@ -0,0 +1,160 @@
1
+ import { ChartDataPoint, ChartTheme } from './chart-types.js';
2
+ import {
3
+ googleTheme,
4
+ seriesaTheme,
5
+ hrTheme,
6
+ figmaTheme,
7
+ notionTheme,
8
+ chalkTheme,
9
+ mintTheme
10
+ } from '../themes/charts.js';
11
+
12
+ /**
13
+ * Lighten a hex color by a percentage
14
+ */
15
+ export function lightenColor(color: string, percent: number): string {
16
+ const num = parseInt(color.replace('#', ''), 16);
17
+ const r = Math.min(255, Math.floor((num >> 16) + ((255 - (num >> 16)) * percent / 100)));
18
+ const g = Math.min(255, Math.floor(((num >> 8) & 0x00FF) + ((255 - ((num >> 8) & 0x00FF)) * percent / 100)));
19
+ const b = Math.min(255, Math.floor((num & 0x0000FF) + ((255 - (num & 0x0000FF)) * percent / 100)));
20
+ return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
21
+ }
22
+
23
+ /**
24
+ * Get theme configuration by name
25
+ */
26
+ export function getThemeConfig(themeName: ChartTheme): any {
27
+ const themes: Record<ChartTheme, any> = {
28
+ google: googleTheme,
29
+ seriesa: seriesaTheme,
30
+ hr: hrTheme,
31
+ figma: figmaTheme,
32
+ notion: notionTheme,
33
+ chalk: chalkTheme,
34
+ mint: mintTheme
35
+ };
36
+ return themes[themeName];
37
+ }
38
+
39
+ /**
40
+ * Create legend HTML element
41
+ */
42
+ export function createLegend(data: ChartDataPoint[], colors: string[], className: string): HTMLElement {
43
+ const legend = document.createElement('div');
44
+ legend.className = `${className}-legend`;
45
+
46
+ data.forEach((point, index) => {
47
+ const color = point.color || colors[index % colors.length];
48
+
49
+ const item = document.createElement('div');
50
+ item.className = `${className}-legend-item`;
51
+
52
+ const swatch = document.createElement('div');
53
+ swatch.className = `${className}-legend-swatch`;
54
+ swatch.style.background = color;
55
+
56
+ const label = document.createElement('span');
57
+ label.className = `${className}-legend-label`;
58
+ label.textContent = point.label;
59
+
60
+ item.appendChild(swatch);
61
+ item.appendChild(label);
62
+ legend.appendChild(item);
63
+ });
64
+
65
+ return legend;
66
+ }
67
+
68
+ /**
69
+ * Create data table HTML element
70
+ */
71
+ export function createDataTable(
72
+ data: ChartDataPoint[],
73
+ xAxisLabel: string,
74
+ yAxisLabel: string,
75
+ className: string,
76
+ chartOrientation?: 'vertical' | 'horizontal'
77
+ ): HTMLElement {
78
+ const table = document.createElement('table');
79
+ table.className = `${className}-table`;
80
+
81
+ const thead = document.createElement('thead');
82
+ const headerRow = document.createElement('tr');
83
+
84
+ // Swap headers based on orientation for bar/area charts
85
+ const columnHeaders = chartOrientation === 'horizontal'
86
+ ? [yAxisLabel || 'Label', xAxisLabel || 'Value']
87
+ : [xAxisLabel || 'Label', yAxisLabel || 'Value'];
88
+
89
+ columnHeaders.forEach(text => {
90
+ const th = document.createElement('th');
91
+ th.textContent = text;
92
+ headerRow.appendChild(th);
93
+ });
94
+ thead.appendChild(headerRow);
95
+ table.appendChild(thead);
96
+
97
+ const tbody = document.createElement('tbody');
98
+ data.forEach(point => {
99
+ const row = document.createElement('tr');
100
+
101
+ const labelCell = document.createElement('td');
102
+ labelCell.textContent = point.label;
103
+
104
+ const valueCell = document.createElement('td');
105
+ valueCell.textContent = point.value.toString();
106
+
107
+ row.appendChild(labelCell);
108
+ row.appendChild(valueCell);
109
+ tbody.appendChild(row);
110
+ });
111
+ table.appendChild(tbody);
112
+
113
+ return table;
114
+ }
115
+
116
+ /**
117
+ * Apply theme styles to document
118
+ */
119
+ export function applyThemeStyles(themeName: ChartTheme, className: string, baseStyles: string): void {
120
+ const theme = getThemeConfig(themeName);
121
+ if (!theme) return;
122
+
123
+ // Inject base styles (once)
124
+ const baseStyleId = `${className}-base-styles`;
125
+ if (!document.getElementById(baseStyleId)) {
126
+ const style = document.createElement('style');
127
+ style.id = baseStyleId;
128
+ style.textContent = baseStyles;
129
+ document.head.appendChild(style);
130
+ }
131
+
132
+ // Inject font (once per theme)
133
+ if (theme.font && !document.querySelector(`link[href="${theme.font}"]`)) {
134
+ const link = document.createElement('link');
135
+ link.rel = 'stylesheet';
136
+ link.href = theme.font;
137
+ document.head.appendChild(link);
138
+ }
139
+
140
+ // Apply theme-specific styles
141
+ const styleId = `${className}-theme-${themeName}`;
142
+ let styleElement = document.getElementById(styleId) as HTMLStyleElement;
143
+
144
+ if (!styleElement) {
145
+ styleElement = document.createElement('style');
146
+ styleElement.id = styleId;
147
+ document.head.appendChild(styleElement);
148
+ }
149
+
150
+ // Generate CSS with theme variables
151
+ const variablesCSS = Object.entries(theme.variables)
152
+ .map(([key, value]) => ` ${key}: ${value};`)
153
+ .join('\n');
154
+
155
+ styleElement.textContent = `
156
+ .${className}.theme-${themeName} {
157
+ ${variablesCSS}
158
+ }
159
+ `;
160
+ }