juxscript 1.0.18 → 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 (44) 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 +99 -101
  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 +711 -264
  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 +174 -125
  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 +78 -28
  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/lib/reactivity/state.ts +13 -299
  44. package/package.json +1 -2
@@ -1,8 +1,6 @@
1
1
  import { getOrCreateContainer } from './helpers.js';
2
+ import { State } from '../reactivity/state.js';
2
3
 
3
- /**
4
- * Tooltip component options
5
- */
6
4
  export interface TooltipOptions {
7
5
  text?: string;
8
6
  position?: 'top' | 'bottom' | 'left' | 'right';
@@ -11,38 +9,32 @@ export interface TooltipOptions {
11
9
  class?: string;
12
10
  }
13
11
 
14
- /**
15
- * Tooltip component state
16
- */
17
12
  type TooltipState = {
18
13
  text: string;
19
- position: string;
20
- trigger: string;
14
+ content?: string;
15
+ position: 'top' | 'bottom' | 'left' | 'right';
16
+ trigger: 'hover' | 'click' | 'focus';
21
17
  style: string;
22
18
  class: string;
19
+ isVisible: boolean;
23
20
  };
24
21
 
25
- /**
26
- * Tooltip component - Contextual help on hover
27
- *
28
- * Usage:
29
- * // Attach to existing element
30
- * jux.tooltip('help-tip', {
31
- * text: 'This is helpful information',
32
- * position: 'top'
33
- * }).attachTo('#help-icon');
34
- *
35
- * // Or render with content
36
- * jux.button('info').label('ℹ️').render('#app');
37
- * jux.tooltip('info-tip').text('More info').attachTo('#info');
38
- */
39
22
  export class Tooltip {
40
23
  state: TooltipState;
41
24
  container: HTMLElement | null = null;
42
25
  _id: string;
43
26
  id: string;
44
- private _targetElement: HTMLElement | null = null;
45
27
  private _tooltipElement: HTMLElement | null = null;
28
+ private _targetElement: HTMLElement | null = null;
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
+ }> = [];
46
38
 
47
39
  constructor(id: string, options: TooltipOptions = {}) {
48
40
  this._id = id;
@@ -53,7 +45,8 @@ export class Tooltip {
53
45
  position: options.position ?? 'top',
54
46
  trigger: options.trigger ?? 'hover',
55
47
  style: options.style ?? '',
56
- class: options.class ?? ''
48
+ class: options.class ?? '',
49
+ isVisible: false
57
50
  };
58
51
  }
59
52
 
@@ -63,6 +56,9 @@ export class Tooltip {
63
56
 
64
57
  text(value: string): this {
65
58
  this.state.text = value;
59
+ if (this._tooltipElement) {
60
+ this._tooltipElement.textContent = value;
61
+ }
66
62
  return this;
67
63
  }
68
64
 
@@ -86,9 +82,18 @@ export class Tooltip {
86
82
  return this;
87
83
  }
88
84
 
89
- /* -------------------------
90
- * Methods
91
- * ------------------------- */
85
+ bind(event: string, handler: Function): this {
86
+ this._bindings.push({ event, handler });
87
+ return this;
88
+ }
89
+
90
+ sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
91
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
92
+ throw new Error(`Tooltip.sync: Expected a State object for property "${property}"`);
93
+ }
94
+ this._syncBindings.push({ property, stateObj, toState, toComponent });
95
+ return this;
96
+ }
92
97
 
93
98
  /**
94
99
  * Attach tooltip to an element
@@ -97,20 +102,16 @@ export class Tooltip {
97
102
  let targetElement: HTMLElement | null = null;
98
103
 
99
104
  if (typeof target === 'string') {
100
- // String selector
101
105
  const el = document.querySelector(target);
102
106
  if (!el || !(el instanceof HTMLElement)) {
103
107
  throw new Error(`Tooltip: Target element "${target}" not found`);
104
108
  }
105
109
  targetElement = el;
106
110
  } else if (target instanceof HTMLElement) {
107
- // Direct HTMLElement
108
111
  targetElement = target;
109
112
  } else if (target && target.container) {
110
- // Jux component with container
111
113
  targetElement = target.container;
112
114
  } else if (target && target._id) {
113
- // Jux component with _id
114
115
  const el = document.getElementById(target._id);
115
116
  if (!el) {
116
117
  throw new Error(`Tooltip: Target element with id "${target._id}" not found`);
@@ -128,16 +129,18 @@ export class Tooltip {
128
129
  }
129
130
 
130
131
  show(): void {
131
- if (this._tooltipElement) {
132
- this._tooltipElement.classList.add('jux-tooltip-visible');
133
- this._positionTooltip();
134
- }
132
+ if (!this._tooltipElement || !this._targetElement) return;
133
+
134
+ this.state.isVisible = true;
135
+ this._tooltipElement.classList.add('jux-tooltip-visible');
136
+ this._positionTooltip();
135
137
  }
136
138
 
137
139
  hide(): void {
138
- if (this._tooltipElement) {
139
- this._tooltipElement.classList.remove('jux-tooltip-visible');
140
- }
140
+ if (!this._tooltipElement) return;
141
+
142
+ this.state.isVisible = false;
143
+ this._tooltipElement.classList.remove('jux-tooltip-visible');
141
144
  }
142
145
 
143
146
  /* -------------------------
@@ -149,8 +152,7 @@ export class Tooltip {
149
152
 
150
153
  const tooltip = document.createElement('div');
151
154
  tooltip.className = `jux-tooltip jux-tooltip-${position}`;
152
- tooltip.id = this._id;
153
- tooltip.setAttribute('role', 'tooltip');
155
+ tooltip.id = `${this._id}-tooltip`;
154
156
  tooltip.textContent = text;
155
157
 
156
158
  if (className) {
@@ -163,6 +165,8 @@ export class Tooltip {
163
165
 
164
166
  document.body.appendChild(tooltip);
165
167
  this._tooltipElement = tooltip;
168
+
169
+ this._injectDefaultStyles();
166
170
  }
167
171
 
168
172
  private _attachEventListeners(): void {
@@ -175,7 +179,7 @@ export class Tooltip {
175
179
  this._targetElement.addEventListener('mouseleave', () => this.hide());
176
180
  } else if (trigger === 'click') {
177
181
  this._targetElement.addEventListener('click', () => {
178
- if (this._tooltipElement?.classList.contains('jux-tooltip-visible')) {
182
+ if (this.state.isVisible) {
179
183
  this.hide();
180
184
  } else {
181
185
  this.show();
@@ -185,6 +189,21 @@ export class Tooltip {
185
189
  this._targetElement.addEventListener('focus', () => this.show());
186
190
  this._targetElement.addEventListener('blur', () => this.hide());
187
191
  }
192
+
193
+ // Wire up sync bindings
194
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
195
+ if (property === 'text') {
196
+ const transformToComponent = toComponent || ((v: any) => String(v));
197
+
198
+ stateObj.subscribe((val: any) => {
199
+ const transformed = transformToComponent(val);
200
+ if (this._tooltipElement) {
201
+ this._tooltipElement.textContent = transformed;
202
+ }
203
+ this.state.text = transformed;
204
+ });
205
+ }
206
+ });
188
207
  }
189
208
 
190
209
  private _positionTooltip(): void {
@@ -220,22 +239,163 @@ export class Tooltip {
220
239
  this._tooltipElement.style.left = `${left + window.scrollX}px`;
221
240
  }
222
241
 
223
- /* -------------------------
224
- * Render (Alternative usage)
225
- * ------------------------- */
242
+ private _injectDefaultStyles(): void {
243
+ const styleId = 'jux-tooltip-styles';
244
+ if (document.getElementById(styleId)) return;
245
+
246
+ const style = document.createElement('style');
247
+ style.id = styleId;
248
+ style.textContent = `
249
+ .jux-tooltip {
250
+ position: absolute;
251
+ background: #1f2937;
252
+ color: white;
253
+ padding: 8px 12px;
254
+ border-radius: 6px;
255
+ font-size: 13px;
256
+ white-space: nowrap;
257
+ z-index: 10000;
258
+ pointer-events: none;
259
+ opacity: 0;
260
+ transition: opacity 0.2s;
261
+ }
262
+
263
+ .jux-tooltip-visible {
264
+ opacity: 1;
265
+ }
266
+
267
+ .jux-tooltip-top::after {
268
+ content: '';
269
+ position: absolute;
270
+ top: 100%;
271
+ left: 50%;
272
+ transform: translateX(-50%);
273
+ border: 5px solid transparent;
274
+ border-top-color: #1f2937;
275
+ }
276
+
277
+ .jux-tooltip-bottom::after {
278
+ content: '';
279
+ position: absolute;
280
+ bottom: 100%;
281
+ left: 50%;
282
+ transform: translateX(-50%);
283
+ border: 5px solid transparent;
284
+ border-bottom-color: #1f2937;
285
+ }
286
+
287
+ .jux-tooltip-left::after {
288
+ content: '';
289
+ position: absolute;
290
+ left: 100%;
291
+ top: 50%;
292
+ transform: translateY(-50%);
293
+ border: 5px solid transparent;
294
+ border-left-color: #1f2937;
295
+ }
296
+
297
+ .jux-tooltip-right::after {
298
+ content: '';
299
+ position: absolute;
300
+ right: 100%;
301
+ top: 50%;
302
+ transform: translateY(-50%);
303
+ border: 5px solid transparent;
304
+ border-right-color: #1f2937;
305
+ }
306
+ `;
307
+ document.head.appendChild(style);
308
+ }
226
309
 
227
310
  render(targetId?: string): this {
311
+ // === 1. SETUP: Get or create container ===
312
+ let container: HTMLElement;
228
313
  if (targetId) {
229
- return this.attachTo(targetId);
314
+ const target = document.querySelector(targetId);
315
+ if (!target || !(target instanceof HTMLElement)) {
316
+ throw new Error(`Tooltip: Target "${targetId}" not found`);
317
+ }
318
+ container = target;
319
+ } else {
320
+ container = getOrCreateContainer(this._id);
230
321
  }
231
- throw new Error('Tooltip requires a target element. Use attachTo(selector) instead.');
322
+ this.container = container;
323
+
324
+ // === 2. PREPARE: Destructure state ===
325
+ const { text, content, position, trigger, style, class: className } = this.state;
326
+
327
+ // === 3. BUILD: Create DOM elements ===
328
+ const wrapper = document.createElement('div');
329
+ wrapper.className = 'jux-tooltip-wrapper';
330
+ wrapper.id = this._id;
331
+ if (className) wrapper.className += ` ${className}`;
332
+ if (style) wrapper.setAttribute('style', style);
333
+
334
+ const contentEl = document.createElement('div');
335
+ contentEl.className = 'jux-tooltip-content';
336
+ contentEl.innerHTML = content;
337
+ wrapper.appendChild(contentEl);
338
+
339
+ const tooltip = document.createElement('div');
340
+ tooltip.className = `jux-tooltip jux-tooltip-${position}`;
341
+ tooltip.id = `${this._id}-tooltip`;
342
+ tooltip.textContent = text;
343
+ tooltip.style.display = 'none';
344
+ wrapper.appendChild(tooltip);
345
+
346
+ // === 4. WIRE: Attach event listeners and sync bindings ===
347
+
348
+ // Tooltip trigger behavior
349
+ if (trigger === 'hover') {
350
+ wrapper.addEventListener('mouseenter', () => {
351
+ tooltip.style.display = 'block';
352
+ });
353
+ wrapper.addEventListener('mouseleave', () => {
354
+ tooltip.style.display = 'none';
355
+ });
356
+ } else if (trigger === 'click') {
357
+ wrapper.addEventListener('click', () => {
358
+ tooltip.style.display = tooltip.style.display === 'none' ? 'block' : 'none';
359
+ });
360
+ }
361
+
362
+ // Wire custom bindings from .bind() calls
363
+ this._bindings.forEach(({ event, handler }) => {
364
+ wrapper.addEventListener(event, handler as EventListener);
365
+ });
366
+
367
+ // Wire sync bindings from .sync() calls
368
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
369
+ if (property === 'text') {
370
+ const transformToComponent = toComponent || ((v: any) => String(v));
371
+
372
+ stateObj.subscribe((val: any) => {
373
+ const transformed = transformToComponent(val);
374
+ tooltip.textContent = transformed;
375
+ this.state.text = transformed;
376
+ });
377
+ }
378
+ else if (property === 'content') {
379
+ const transformToComponent = toComponent || ((v: any) => String(v));
380
+
381
+ stateObj.subscribe((val: any) => {
382
+ const transformed = transformToComponent(val);
383
+ contentEl.innerHTML = transformed;
384
+ this.state.content = transformed;
385
+ });
386
+ }
387
+ });
388
+
389
+ // === 5. RENDER: Append to DOM and finalize ===
390
+ container.appendChild(wrapper);
391
+ return this;
232
392
  }
233
393
 
234
394
  renderTo(juxComponent: any): this {
235
395
  if (!juxComponent?._id) {
236
396
  throw new Error('Tooltip.renderTo: Invalid component');
237
397
  }
238
- return this.attachTo(`#${juxComponent._id}`);
398
+ return this.render(`#${juxComponent._id}`);
239
399
  }
240
400
  }
241
401
 
package/lib/jux.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { state } from './reactivity/state.js';
7
7
  import { Data, data } from './components/data.js';
8
- import { table, Table, type TableOptions, type TableColumn } from './components/table.js';
8
+ import { table, Table, type TableOptions } from './components/table.js';
9
9
  import { hero, Hero, type HeroOptions } from './components/hero.js';
10
10
  import { card, Card, type CardOptions } from './components/card.js';
11
11
  import { button, Button, type ButtonOptions } from './components/button.js';
@@ -55,6 +55,12 @@ import { areachartsmooth, AreaChartSmooth, type AreaChartSmoothOptions, AreaChar
55
55
  import { doughnutchart, DoughnutChart, type DoughnutChartOptions, type DoughnutChartDataPoint } from './components/doughnutchart.js';
56
56
  import { kpicard, KPICard, type KPICardOptions } from './components/kpicard.js';
57
57
  import { divider, Divider, type DividerOptions } from './components/divider.js';
58
+ import { icon as iconComponent, Icon, type IconOptions } from './components/icon.js';
59
+ import { renderIcon, renderEmoji } from './components/icons.js';
60
+
61
+ // NEW: Export chart utilities
62
+ export * from './components/chart-types.js';
63
+ export * from './components/chart-utils.js';
58
64
 
59
65
  /* -------------------------
60
66
  * Type Exports
@@ -79,7 +85,6 @@ export type {
79
85
  NavItem,
80
86
  ViewOptions,
81
87
  TableOptions,
82
- TableColumn,
83
88
  ChartOptions,
84
89
  CodeOptions,
85
90
  InputOptions,
@@ -118,7 +123,8 @@ export type {
118
123
  DoughnutChartOptions,
119
124
  DoughnutChartDataPoint,
120
125
  KPICardOptions,
121
- DividerOptions
126
+ DividerOptions,
127
+ IconOptions
122
128
  };
123
129
 
124
130
  /* -------------------------
@@ -175,7 +181,8 @@ export {
175
181
  AreaChartSmooth,
176
182
  DoughnutChart,
177
183
  KPICard,
178
- Divider
184
+ Divider,
185
+ Icon
179
186
  };
180
187
 
181
188
  /* -------------------------
@@ -188,6 +195,11 @@ export interface JuxAPI {
188
195
  param(name: string): string | null;
189
196
  req: typeof req;
190
197
 
198
+ // Icon utilities
199
+ icon: typeof renderIcon;
200
+ emoji: typeof renderEmoji;
201
+ iconComponent: typeof iconComponent;
202
+
191
203
  // Component factories
192
204
  data: typeof data;
193
205
  table: typeof table;
@@ -266,6 +278,11 @@ class Jux implements JuxAPI {
266
278
  // Request utilities
267
279
  req = req;
268
280
 
281
+ // Icon utilities
282
+ icon = renderIcon;
283
+ emoji = renderEmoji;
284
+ iconComponent = iconComponent;
285
+
269
286
  // Component factory methods
270
287
  data = data;
271
288
  table = table;
@@ -335,5 +352,7 @@ export {
335
352
  jux,
336
353
  state,
337
354
  req,
338
- ErrorHandler
355
+ ErrorHandler,
356
+ renderIcon,
357
+ renderEmoji
339
358
  };