juxscript 1.0.59 → 1.0.60

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/bin/cli.js CHANGED
@@ -215,6 +215,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
215
215
  fs.mkdirSync(PATHS.frontendDist, { recursive: true });
216
216
 
217
217
  // Step 1: Generate documentation
218
+ /*
218
219
  const docsStartTime = performance.now();
219
220
  let docsTime = 0; // ✅ Declare with default value
220
221
  console.log('📚 Generating documentation...');
@@ -226,6 +227,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
226
227
  docsTime = performance.now() - docsStartTime; // ✅ Still calculate time even on error
227
228
  console.warn(`⚠️ Failed to generate docs (${docsTime.toFixed(0)}ms):`, error.message);
228
229
  }
230
+ */
229
231
 
230
232
  // Step 2: Copy jux lib to frontend dist
231
233
  const libStartTime = performance.now();
@@ -302,7 +304,7 @@ async function buildProject(isServe = false, wsPort = 3001) {
302
304
  // ✅ Build summary with timing breakdown
303
305
  console.log(`📊 Build Summary:`);
304
306
  console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
305
- console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
307
+ // console.log(` Documentation: ${docsTime.toFixed(0)}ms`);
306
308
  console.log(` Library copy: ${libTime.toFixed(0)}ms`);
307
309
  console.log(` Presets copy: ${presetsTime.toFixed(0)}ms`);
308
310
  console.log(` Assets copy: ${assetsTime.toFixed(0)}ms`);
@@ -350,10 +352,10 @@ async function buildProject(isServe = false, wsPort = 3001) {
350
352
 
351
353
  // ✅ Copy presets/default/ directly to jux/ (no presets subfolder!)
352
354
  const defaultPresetSrc = path.join(PATHS.packageRoot, 'presets', 'default');
353
-
355
+
354
356
  if (fs.existsSync(defaultPresetSrc)) {
355
357
  console.log('📦 Copying default preset boilerplate...');
356
-
358
+
357
359
  const entries = fs.readdirSync(defaultPresetSrc, { withFileTypes: true });
358
360
  let copiedCount = 0;
359
361
 
@@ -401,7 +403,7 @@ jux.paragraph('counter')
401
403
  const pkgPath = path.join(PATHS.projectRoot, 'package.json');
402
404
  if (!fs.existsSync(pkgPath)) {
403
405
  const projectName = path.basename(PATHS.projectRoot).toLowerCase().replace(/[^a-z0-9-]/g, '-');
404
-
406
+
405
407
  const pkgContent = {
406
408
  "name": projectName,
407
409
  "version": "0.1.0",
@@ -429,14 +431,14 @@ jux.paragraph('counter')
429
431
 
430
432
  // ✅ Copy juxconfig to root
431
433
  const configExampleSrc = path.join(PATHS.packageRoot, 'juxconfig.example.js');
432
-
434
+
433
435
  if (fs.existsSync(configExampleSrc)) {
434
436
  const configDest = path.join(PATHS.projectRoot, 'juxconfig.js');
435
437
  if (!fs.existsSync(configDest)) {
436
438
  fs.copyFileSync(configExampleSrc, configDest);
437
439
  console.log('+ Created juxconfig.js');
438
440
  }
439
-
441
+
440
442
  // ❌ REMOVE: Don't copy example - it's available in node_modules
441
443
  // const configExampleDest = path.join(PATHS.projectRoot, 'juxconfig.example.js');
442
444
  // if (!fs.existsSync(configExampleDest)) {
package/docs/grid.png ADDED
Binary file
@@ -16,12 +16,21 @@ export default {
16
16
  // Application settings
17
17
  appName: 'My JUX App',
18
18
  autoRoutes: true,
19
-
20
19
  // Directories
21
20
  sourceDir: 'jux',
22
21
  distDir: '.jux-dist',
23
22
 
24
- // External services
23
+ // External services - used by jux.fetch and jux.fetch.serviceClient()
24
+ // Each service becomes available as a baseUrl for fetch requests
25
+ //
26
+ // Usage in components:
27
+ // const db = jux.fetch.serviceClient('database');
28
+ // const { data } = await db.fetch('/users').send();
29
+ //
30
+ // // Or directly:
31
+ // const { data } = await jux.fetch('/users')
32
+ // .header('Authorization', 'Bearer token')
33
+ // .send();
25
34
  services: {
26
35
  database: 'http://localhost:4000/api/db',
27
36
  auth: 'http://localhost:4000/api/auth',
@@ -34,6 +43,10 @@ export default {
34
43
  ws: 3001
35
44
  },
36
45
 
46
+ // Fetch configuration
47
+ fetchTimeout: 30000, // milliseconds
48
+ fetchLog: false, // true, false, or 'errors' for error-only logging
49
+
37
50
  // Build options
38
51
  build: {
39
52
  minify: false,
@@ -41,9 +54,18 @@ export default {
41
54
  },
42
55
 
43
56
  // Bootstrap functions (run on startup)
57
+ // Perfect place to initialize services and auth
44
58
  bootstrap: [
59
+ // Example: Setup fetch with service configuration
60
+ // async function initFetch() {
61
+ // await jux.fetch.setupConfig();
62
+ // console.log('✅ Fetch configured');
63
+ // },
64
+
65
+ // Example: Initialize authentication
45
66
  // async function initAuth() {
46
67
  // console.log('🔐 Initializing auth...');
47
68
  // },
48
69
  ]
49
70
  };
71
+
@@ -394,4 +394,24 @@ export abstract class BaseComponent<TState extends Record<string, any>> {
394
394
  }
395
395
  return this.render(`#${juxComponent._id}`);
396
396
  }
397
+
398
+ /* ═════════════════════════════════════════════════════════════════
399
+ * PROPS ACCESSOR - Read-only access to component state
400
+ * ═════════════════════════════════════════════════════════════════ */
401
+
402
+ /**
403
+ * ✅ Read-only accessor for component state
404
+ * Provides clear separation between setters (fluent methods) and getters
405
+ *
406
+ * @example
407
+ * const myCard = card('example')
408
+ * .title('Hello') // ✅ SETTER (fluent)
409
+ * .content('World'); // ✅ SETTER (fluent)
410
+ *
411
+ * console.log(myCard.props.title); // ✅ GETTER: 'Hello'
412
+ * console.log(myCard.props.content); // ✅ GETTER: 'World'
413
+ */
414
+ get props(): Readonly<TState> {
415
+ return this.state as Readonly<TState>;
416
+ }
397
417
  }
@@ -1,64 +1,45 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
2
 
3
- // Event definitions
3
+ // Event definitions - Container is a layout wrapper that can be clicked
4
4
  const TRIGGER_EVENTS = [] as const;
5
- const CALLBACK_EVENTS = [] as const;
6
-
7
- // Deprecation warning constant
8
- const DEPRECATION_WARNING = (feature: string) =>
9
- `[JUX Deprecation Warning] Container.${feature} will be removed in v1.0.30+ (January 30, 2026). Please use CSS or the .style() method instead. See: https://juxscript.com/docs/migration`;
10
-
11
- // Track which warnings we've already shown (to avoid spam)
12
- const _shownWarnings = new Set<string>();
13
-
14
- function warnOnce(feature: string): void {
15
- if (!_shownWarnings.has(feature)) {
16
- console.warn(DEPRECATION_WARNING(feature));
17
- _shownWarnings.add(feature);
18
- }
19
- }
5
+ const CALLBACK_EVENTS = ['click'] as const;
20
6
 
7
+ /**
8
+ * Container options
9
+ */
21
10
  export interface ContainerOptions {
22
- direction?: 'row' | 'column'; // should deprecate.
23
- gap?: number | string; // should deprecate.
24
- wrap?: boolean; // should deprecate.
25
- align?: 'start' | 'center' | 'end' | 'stretch'; // should deprecate.
26
- justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'; // should deprecate.
27
- padding?: string; // should deprecate.
28
- style?: string; // should deprecate. -> inherited from BaseComponent
29
- class?: string; // should deprecate. -> inherited from BaseComponent
11
+ class?: string;
12
+ style?: string;
13
+ direction?: 'row' | 'column';
14
+ gap?: string | number;
15
+ align?: 'start' | 'center' | 'end' | 'stretch';
16
+ justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
30
17
  }
31
18
 
19
+ /**
20
+ * Container state
21
+ */
32
22
  type ContainerState = {
33
- direction: string; // should deprecate.
34
- gap: number | string; // should deprecate.
35
- wrap: boolean; // should deprecate.
36
- align: string; // should deprecate.
37
- justify: string; // should deprecate.
38
- padding: string; // should deprecate.
39
- style: string; // should deprecate. -> inherited from BaseComponent
40
- class: string; // should deprecate. -> inherited from BaseComponent
23
+ class: string;
24
+ style: string;
25
+ direction?: 'row' | 'column';
26
+ gap?: string | number;
27
+ align?: string;
28
+ justify?: string;
41
29
  };
42
30
 
31
+ /**
32
+ * Container component - a simple div container for grouping elements
33
+ */
43
34
  export class Container extends BaseComponent<ContainerState> {
44
35
  constructor(id: string, options: ContainerOptions = {}) {
45
- // Warn for deprecated options
46
- if (options.direction !== undefined) warnOnce('direction (option)');
47
- if (options.gap !== undefined) warnOnce('gap (option)');
48
- if (options.wrap !== undefined) warnOnce('wrap (option)');
49
- if (options.align !== undefined) warnOnce('align (option)');
50
- if (options.justify !== undefined) warnOnce('justify (option)');
51
- if (options.padding !== undefined) warnOnce('padding (option)');
52
-
53
36
  super(id, {
54
- direction: options.direction ?? 'column',// should deprecate.
55
- gap: options.gap ?? 0, //should deprecate.
56
- wrap: options.wrap ?? false, //should deprecate.
57
- align: options.align ?? 'stretch', //should deprecate.
58
- justify: options.justify ?? 'start', //should deprecate.
59
- padding: options.padding ?? '0', //should deprecate.
60
- style: options.style ?? '', //should deprecate. -> inherited from BaseComponent
61
- class: options.class ?? '' //should deprecate. -> inherited from BaseComponent
37
+ class: options.class ?? '',
38
+ style: options.style ?? '',
39
+ direction: options.direction,
40
+ gap: options.gap,
41
+ align: options.align,
42
+ justify: options.justify
62
43
  });
63
44
  }
64
45
 
@@ -70,113 +51,90 @@ export class Container extends BaseComponent<ContainerState> {
70
51
  return CALLBACK_EVENTS;
71
52
  }
72
53
 
73
- /* ═════════════════════════════════════════════════════════════════
74
- * FLUENT API
75
- * ═════════════════════════════════════════════════════════════════ */
54
+ /* -------------------------
55
+ * Fluent API
56
+ * ------------------------- */
76
57
 
77
58
  // ✅ Inherited from BaseComponent:
78
59
  // - style(), class()
60
+ // - bind(), sync(), renderTo()
79
61
  // - addClass(), removeClass(), toggleClass()
80
62
  // - visible(), show(), hide()
81
63
  // - attr(), attrs(), removeAttr()
82
64
  // - disabled(), enable(), disable()
83
- // - bind(), sync(), renderTo()
84
65
 
85
66
  direction(value: 'row' | 'column'): this {
86
- warnOnce('direction()');
87
67
  this.state.direction = value;
88
68
  return this;
89
69
  }
90
70
 
91
- gap(value: number | string): this {
92
- warnOnce('gap()');
71
+ gap(value: string | number): this {
93
72
  this.state.gap = value;
94
73
  return this;
95
74
  }
96
75
 
97
- wrap(value: boolean): this {
98
- warnOnce('wrap()');
99
- this.state.wrap = value;
100
- return this;
101
- }
102
-
103
76
  align(value: 'start' | 'center' | 'end' | 'stretch'): this {
104
- warnOnce('align()');
105
77
  this.state.align = value;
106
78
  return this;
107
79
  }
108
80
 
109
81
  justify(value: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'): this {
110
- warnOnce('justify()');
111
82
  this.state.justify = value;
112
83
  return this;
113
84
  }
114
85
 
115
- padding(value: string): this {
116
- warnOnce('padding()');
117
- this.state.padding = value;
118
- return this;
119
- }
120
-
121
- /* ═════════════════════════════════════════════════════════════════
122
- * RENDER
123
- * ═════════════════════════════════════════════════════════════════ */
86
+ /* -------------------------
87
+ * Render
88
+ * ------------------------- */
124
89
 
125
90
  render(targetId?: string): this {
126
91
  const container = this._setupContainer(targetId);
127
92
 
128
- const { direction, gap, wrap, align, justify, padding, style, class: className } = this.state;
129
-
130
- const wrapper = document.createElement('div');
131
- wrapper.className = 'jux-container';
132
- wrapper.id = this._id;
133
- if (className) wrapper.className += ` ${className}`;
134
-
135
- this._wireStandardEvents(wrapper);
136
-
137
- // Wire sync bindings
138
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
139
- const transform = toComponent || ((v: any) => v);
140
-
141
- stateObj.subscribe((val: any) => {
142
- const transformed = transform(val);
143
-
144
- if (property === 'direction') {
145
- warnOnce('direction (sync)');
146
- this.state.direction = String(transformed);
147
- wrapper.style.flexDirection = this.state.direction;
148
- } else if (property === 'gap') {
149
- warnOnce('gap (sync)');
150
- this.state.gap = transformed;
151
- const gapVal = typeof transformed === 'number' ? `${transformed}px` : transformed;
152
- wrapper.style.gap = gapVal;
153
- } else if (property === 'wrap') {
154
- warnOnce('wrap (sync)');
155
- this.state.wrap = Boolean(transformed);
156
- wrapper.style.flexWrap = this.state.wrap ? 'wrap' : 'nowrap';
157
- } else if (property === 'align') {
158
- warnOnce('align (sync)');
159
- this.state.align = String(transformed);
160
- wrapper.style.alignItems = this.state.align;
161
- } else if (property === 'justify') {
162
- warnOnce('justify (sync)');
163
- this.state.justify = String(transformed);
164
- wrapper.style.justifyContent = this.state.justify;
165
- } else if (property === 'padding') {
166
- warnOnce('padding (sync)');
167
- this.state.padding = String(transformed);
168
- wrapper.style.padding = this.state.padding;
169
- }
170
-
171
- });
93
+ const { class: className, style, direction, gap, align, justify } = this.state;
172
94
 
173
- });
95
+ const div = document.createElement('div');
96
+ div.id = this._id;
97
+
98
+ // Always include jux-container class, append custom classes
99
+ div.className = className ? `jux-container ${className}` : 'jux-container';
100
+
101
+ // Only apply flex styles if any flex properties are set
102
+ const usesFlexbox = direction || gap !== undefined || align || justify;
103
+
104
+ let computedStyle = style;
105
+
106
+ if (usesFlexbox) {
107
+ const flexStyles: string[] = ['display: flex', 'width: auto'];
108
+
109
+ if (direction) flexStyles.push(`flex-direction: ${direction}`);
110
+
111
+ if (gap !== undefined) {
112
+ const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
113
+ flexStyles.push(`gap: ${gapValue}`);
114
+ }
115
+ // Only set align-items if explicitly specified
116
+ if (align) flexStyles.push(`align-items: ${align}`);
117
+ // Only set justify-content if explicitly specified
118
+ if (justify) flexStyles.push(`justify-content: ${justify}`);
119
+
120
+ computedStyle = `${flexStyles.join('; ')}; ${style}`.trim();
121
+ }
122
+
123
+ if (computedStyle) {
124
+ div.setAttribute('style', computedStyle);
125
+ }
126
+
127
+ this._wireStandardEvents(div);
128
+
129
+ container.appendChild(div);
174
130
 
175
- container.appendChild(wrapper);
176
131
  return this;
177
132
  }
178
133
  }
179
134
 
135
+ /**
136
+ * Factory helper
137
+ */
180
138
  export function container(id: string, options: ContainerOptions = {}): Container {
181
139
  return new Container(id, options);
182
140
  }
@@ -578,7 +578,7 @@
578
578
  {
579
579
  "name": "Container",
580
580
  "category": "UI Components",
581
- "description": "Container component",
581
+ "description": "Container options",
582
582
  "constructor": "jux.container(id: string, options: ContainerOptions = {})",
583
583
  "fluentMethods": [
584
584
  {
@@ -593,12 +593,6 @@
593
593
  "returns": "this",
594
594
  "description": "Set gap"
595
595
  },
596
- {
597
- "name": "wrap",
598
- "params": "(value)",
599
- "returns": "this",
600
- "description": "Set wrap"
601
- },
602
596
  {
603
597
  "name": "align",
604
598
  "params": "(value)",
@@ -611,12 +605,6 @@
611
605
  "returns": "this",
612
606
  "description": "Set justify"
613
607
  },
614
- {
615
- "name": "padding",
616
- "params": "(value)",
617
- "returns": "this",
618
- "description": "Set padding"
619
- },
620
608
  {
621
609
  "name": "render",
622
610
  "params": "(targetId?)",
@@ -1278,111 +1266,6 @@
1278
1266
  ],
1279
1267
  "example": "jux.input('id').render()"
1280
1268
  },
1281
- {
1282
- "name": "Kpicard",
1283
- "category": "UI Components",
1284
- "description": "KPI card options",
1285
- "constructor": "jux.kpicard(id: string, options: KPICardOptions = {})",
1286
- "fluentMethods": [
1287
- {
1288
- "name": "title",
1289
- "params": "(value)",
1290
- "returns": "this",
1291
- "description": "Set title"
1292
- },
1293
- {
1294
- "name": "value",
1295
- "params": "(value)",
1296
- "returns": "this",
1297
- "description": "Set value"
1298
- },
1299
- {
1300
- "name": "delta",
1301
- "params": "(value)",
1302
- "returns": "this",
1303
- "description": "Set delta"
1304
- },
1305
- {
1306
- "name": "prefix",
1307
- "params": "(value)",
1308
- "returns": "this",
1309
- "description": "Set prefix"
1310
- },
1311
- {
1312
- "name": "suffix",
1313
- "params": "(value)",
1314
- "returns": "this",
1315
- "description": "Set suffix"
1316
- },
1317
- {
1318
- "name": "width",
1319
- "params": "(value)",
1320
- "returns": "this",
1321
- "description": "Set width"
1322
- },
1323
- {
1324
- "name": "height",
1325
- "params": "(value)",
1326
- "returns": "this",
1327
- "description": "Set height"
1328
- },
1329
- {
1330
- "name": "theme",
1331
- "params": "(value)",
1332
- "returns": "this",
1333
- "description": "Set theme"
1334
- },
1335
- {
1336
- "name": "styleMode",
1337
- "params": "(value)",
1338
- "returns": "this",
1339
- "description": "Set styleMode"
1340
- },
1341
- {
1342
- "name": "animate",
1343
- "params": "(value)",
1344
- "returns": "this",
1345
- "description": "Set animate"
1346
- },
1347
- {
1348
- "name": "animationDuration",
1349
- "params": "(value)",
1350
- "returns": "this",
1351
- "description": "Set animationDuration"
1352
- },
1353
- {
1354
- "name": "borderRadius",
1355
- "params": "(value)",
1356
- "returns": "this",
1357
- "description": "Set borderRadius"
1358
- },
1359
- {
1360
- "name": "showAccentBar",
1361
- "params": "(value)",
1362
- "returns": "this",
1363
- "description": "Set showAccentBar"
1364
- },
1365
- {
1366
- "name": "class",
1367
- "params": "(value)",
1368
- "returns": "this",
1369
- "description": "Set class"
1370
- },
1371
- {
1372
- "name": "style",
1373
- "params": "(value)",
1374
- "returns": "this",
1375
- "description": "Set style"
1376
- },
1377
- {
1378
- "name": "render",
1379
- "params": "(container)",
1380
- "returns": "this",
1381
- "description": "Set render"
1382
- }
1383
- ],
1384
- "example": "jux.kpicard('users-kpi')"
1385
- },
1386
1269
  {
1387
1270
  "name": "Loading",
1388
1271
  "category": "UI Components",
@@ -2071,5 +1954,5 @@
2071
1954
  }
2072
1955
  ],
2073
1956
  "version": "1.0.0",
2074
- "lastUpdated": "2026-01-29T04:55:18.239Z"
1957
+ "lastUpdated": "2026-01-29T15:56:48.374Z"
2075
1958
  }