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.
- package/lib/components/alert.ts +124 -128
- package/lib/components/areachart.ts +169 -287
- package/lib/components/areachartsmooth.ts +2 -2
- package/lib/components/badge.ts +63 -72
- package/lib/components/barchart.ts +120 -48
- package/lib/components/button.ts +92 -60
- package/lib/components/card.ts +97 -121
- package/lib/components/chart-types.ts +159 -0
- package/lib/components/chart-utils.ts +160 -0
- package/lib/components/chart.ts +628 -48
- package/lib/components/checkbox.ts +137 -51
- package/lib/components/code.ts +89 -75
- package/lib/components/container.ts +1 -1
- package/lib/components/datepicker.ts +93 -78
- package/lib/components/dialog.ts +163 -130
- package/lib/components/divider.ts +111 -193
- package/lib/components/docs-data.json +697 -274
- package/lib/components/doughnutchart.ts +125 -57
- package/lib/components/dropdown.ts +172 -85
- package/lib/components/element.ts +66 -61
- package/lib/components/fileupload.ts +142 -171
- package/lib/components/heading.ts +64 -21
- package/lib/components/hero.ts +109 -34
- package/lib/components/icon.ts +247 -0
- package/lib/components/icons.ts +174 -0
- package/lib/components/include.ts +77 -2
- package/lib/components/input.ts +105 -53
- package/lib/components/list.ts +120 -79
- package/lib/components/menu.ts +97 -2
- package/lib/components/modal.ts +144 -63
- package/lib/components/nav.ts +153 -52
- package/lib/components/paragraph.ts +54 -91
- package/lib/components/progress.ts +83 -107
- package/lib/components/radio.ts +151 -52
- package/lib/components/select.ts +110 -102
- package/lib/components/sidebar.ts +148 -105
- package/lib/components/switch.ts +124 -125
- package/lib/components/table.ts +214 -137
- package/lib/components/tabs.ts +194 -113
- package/lib/components/theme-toggle.ts +38 -7
- package/lib/components/tooltip.ts +207 -47
- package/lib/jux.ts +24 -5
- package/package.json +1 -2
package/lib/components/card.ts
CHANGED
|
@@ -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
|
-
|
|
11
|
-
variant?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
this.state.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
203
|
-
throw new Error('Card.renderTo: Invalid component
|
|
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
|
+
}
|