juxscript 1.0.2 → 1.0.4
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/README.md +37 -92
- package/bin/cli.js +57 -56
- package/lib/components/alert.ts +240 -0
- package/lib/components/app.ts +216 -82
- package/lib/components/badge.ts +164 -0
- package/lib/components/button.ts +188 -53
- package/lib/components/card.ts +75 -61
- package/lib/components/chart.ts +17 -15
- package/lib/components/checkbox.ts +228 -0
- package/lib/components/code.ts +66 -152
- package/lib/components/container.ts +104 -208
- package/lib/components/data.ts +1 -3
- package/lib/components/datepicker.ts +226 -0
- package/lib/components/dialog.ts +258 -0
- package/lib/components/docs-data.json +1697 -388
- package/lib/components/dropdown.ts +244 -0
- package/lib/components/element.ts +271 -0
- package/lib/components/fileupload.ts +319 -0
- package/lib/components/footer.ts +37 -18
- package/lib/components/header.ts +53 -33
- package/lib/components/heading.ts +119 -0
- package/lib/components/helpers.ts +34 -0
- package/lib/components/hero.ts +57 -31
- package/lib/components/include.ts +292 -0
- package/lib/components/input.ts +166 -78
- package/lib/components/layout.ts +144 -18
- package/lib/components/list.ts +83 -74
- package/lib/components/loading.ts +263 -0
- package/lib/components/main.ts +43 -17
- package/lib/components/menu.ts +108 -24
- package/lib/components/modal.ts +50 -21
- package/lib/components/nav.ts +60 -18
- package/lib/components/paragraph.ts +111 -0
- package/lib/components/progress.ts +276 -0
- package/lib/components/radio.ts +236 -0
- package/lib/components/req.ts +300 -0
- package/lib/components/script.ts +33 -74
- package/lib/components/select.ts +247 -0
- package/lib/components/sidebar.ts +86 -36
- package/lib/components/style.ts +47 -70
- package/lib/components/switch.ts +261 -0
- package/lib/components/table.ts +47 -24
- package/lib/components/tabs.ts +105 -63
- package/lib/components/theme-toggle.ts +361 -0
- package/lib/components/token-calculator.ts +380 -0
- package/lib/components/tooltip.ts +244 -0
- package/lib/components/view.ts +36 -20
- package/lib/components/write.ts +284 -0
- package/lib/globals.d.ts +21 -0
- package/lib/jux.ts +172 -68
- package/lib/presets/notion.css +521 -0
- package/lib/presets/notion.jux +27 -0
- package/lib/reactivity/state.ts +364 -0
- package/machinery/compiler.js +126 -38
- package/machinery/generators/html.js +2 -3
- package/machinery/server.js +2 -2
- package/package.json +29 -3
- package/lib/components/import.ts +0 -430
- package/lib/components/node.ts +0 -200
- package/lib/components/reactivity.js +0 -104
- package/lib/components/theme.ts +0 -97
- package/lib/layouts/notion.css +0 -258
- package/lib/styles/base-theme.css +0 -186
- package/lib/styles/dark-theme.css +0 -144
- package/lib/styles/light-theme.css +0 -144
- package/lib/styles/tokens/dark.css +0 -86
- package/lib/styles/tokens/light.css +0 -86
- package/lib/templates/index.juxt +0 -33
- package/lib/themes/dark.css +0 -86
- package/lib/themes/light.css +0 -86
- /package/lib/{styles → presets}/global.css +0 -0
|
@@ -1,184 +1,124 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Container options
|
|
5
5
|
*/
|
|
6
6
|
export interface ContainerOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
gap?: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
wrap?: boolean;
|
|
14
|
-
children?: any[];
|
|
7
|
+
class?: string;
|
|
8
|
+
style?: string;
|
|
9
|
+
direction?: 'row' | 'column';
|
|
10
|
+
gap?: string | number;
|
|
11
|
+
align?: 'start' | 'center' | 'end' | 'stretch';
|
|
12
|
+
justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
16
|
* Container state
|
|
19
17
|
*/
|
|
20
18
|
type ContainerState = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
gap
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
wrap: boolean;
|
|
28
|
-
children: any[];
|
|
19
|
+
class: string;
|
|
20
|
+
style: string;
|
|
21
|
+
direction?: 'row' | 'column';
|
|
22
|
+
gap?: string | number;
|
|
23
|
+
align?: string;
|
|
24
|
+
justify?: string;
|
|
29
25
|
};
|
|
30
26
|
|
|
31
27
|
/**
|
|
32
|
-
* Container component -
|
|
28
|
+
* Container component - a simple div container for grouping elements
|
|
33
29
|
*
|
|
34
30
|
* Usage:
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* direction: 'vertical',
|
|
38
|
-
* justify: 'space-between',
|
|
39
|
-
* align: 'center'
|
|
40
|
-
* });
|
|
41
|
-
* container.add(component1);
|
|
42
|
-
* container.add([component2, component3]);
|
|
43
|
-
* await container.render();
|
|
31
|
+
* // Plain container
|
|
32
|
+
* jux.container('wrapper').render('#app');
|
|
44
33
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* - wrap: boolean (default: false)
|
|
51
|
-
* - gap: string (default: '16px')
|
|
52
|
-
*
|
|
53
|
-
* GRID Layout Options:
|
|
54
|
-
* - layout: 'grid'
|
|
55
|
-
* - minWidth: string - for auto-fit (default: '250px')
|
|
56
|
-
* - gap: string (default: '16px')
|
|
57
|
-
* - align: 'start' | 'end' | 'center' | 'stretch' (default: 'stretch')
|
|
58
|
-
* - justify: 'start' | 'end' | 'center' | 'stretch' (default: 'stretch')
|
|
34
|
+
* // Flex container with layout
|
|
35
|
+
* jux.container('toolbar')
|
|
36
|
+
* .direction('row')
|
|
37
|
+
* .gap(16)
|
|
38
|
+
* .render('#app');
|
|
59
39
|
*/
|
|
60
|
-
export class Container
|
|
61
|
-
state
|
|
40
|
+
export class Container {
|
|
41
|
+
state: ContainerState;
|
|
62
42
|
container: HTMLElement | null = null;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
43
|
+
_id: string;
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
constructor(id: string, options: ContainerOptions = {}) {
|
|
47
|
+
this._id = id;
|
|
48
|
+
this.id = id;
|
|
49
|
+
|
|
50
|
+
this.state = {
|
|
51
|
+
class: options.class ?? '',
|
|
52
|
+
style: options.style ?? '',
|
|
53
|
+
direction: options.direction,
|
|
54
|
+
gap: options.gap,
|
|
55
|
+
align: options.align,
|
|
56
|
+
justify: options.justify
|
|
57
|
+
};
|
|
78
58
|
}
|
|
79
59
|
|
|
80
60
|
/* -------------------------
|
|
81
61
|
* Fluent API
|
|
82
62
|
* ------------------------- */
|
|
83
63
|
|
|
84
|
-
|
|
85
|
-
this.state.
|
|
64
|
+
class(value: string): this {
|
|
65
|
+
this.state.class = value;
|
|
86
66
|
return this;
|
|
87
67
|
}
|
|
88
68
|
|
|
89
|
-
|
|
90
|
-
this.state.
|
|
69
|
+
style(value: string): this {
|
|
70
|
+
this.state.style = value;
|
|
91
71
|
return this;
|
|
92
72
|
}
|
|
93
73
|
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Set flex direction (row = horizontal, column = vertical)
|
|
76
|
+
* Automatically enables flexbox when called
|
|
77
|
+
*/
|
|
78
|
+
direction(value: 'row' | 'column'): this {
|
|
79
|
+
this.state.direction = value;
|
|
96
80
|
return this;
|
|
97
81
|
}
|
|
98
82
|
|
|
99
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Set gap between children
|
|
85
|
+
* Automatically enables flexbox when called
|
|
86
|
+
* @param value - Gap size (number = px, string = any CSS unit)
|
|
87
|
+
*/
|
|
88
|
+
gap(value: string | number): this {
|
|
100
89
|
this.state.gap = value;
|
|
101
90
|
return this;
|
|
102
91
|
}
|
|
103
92
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
align(value: 'start' | 'end' | 'center' | 'stretch' | 'baseline'): this {
|
|
93
|
+
/**
|
|
94
|
+
* Set alignment of children along cross axis
|
|
95
|
+
* Automatically enables flexbox when called
|
|
96
|
+
*/
|
|
97
|
+
align(value: 'start' | 'center' | 'end' | 'stretch'): this {
|
|
110
98
|
this.state.align = value;
|
|
111
99
|
return this;
|
|
112
100
|
}
|
|
113
101
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
* Add children
|
|
121
|
-
* ------------------------- */
|
|
122
|
-
|
|
123
|
-
add(component: any | any[]): this {
|
|
124
|
-
if (Array.isArray(component)) {
|
|
125
|
-
this.state.children.push(...component);
|
|
126
|
-
} else {
|
|
127
|
-
this.state.children.push(component);
|
|
128
|
-
}
|
|
102
|
+
/**
|
|
103
|
+
* Set justification of children along main axis
|
|
104
|
+
* Automatically enables flexbox when called
|
|
105
|
+
*/
|
|
106
|
+
justify(value: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'): this {
|
|
107
|
+
this.state.justify = value;
|
|
129
108
|
return this;
|
|
130
109
|
}
|
|
131
110
|
|
|
132
|
-
/* -------------------------
|
|
133
|
-
* Layout styles
|
|
134
|
-
* ------------------------- */
|
|
135
|
-
|
|
136
|
-
private _applyFlexStyles(containerEl: HTMLElement): void {
|
|
137
|
-
const { direction, gap, justify, align, wrap } = this.state;
|
|
138
|
-
containerEl.style.cssText = `
|
|
139
|
-
display: flex;
|
|
140
|
-
flex-direction: ${direction === 'horizontal' ? 'row' : 'column'};
|
|
141
|
-
justify-content: ${justify};
|
|
142
|
-
align-items: ${align};
|
|
143
|
-
flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
|
|
144
|
-
gap: ${gap};
|
|
145
|
-
`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private _applyGridStyles(containerEl: HTMLElement): void {
|
|
149
|
-
const { minWidth, gap, direction } = this.state;
|
|
150
|
-
|
|
151
|
-
if (direction === 'horizontal') {
|
|
152
|
-
// For horizontal grid, use auto-fit columns
|
|
153
|
-
containerEl.style.cssText = `
|
|
154
|
-
display: grid;
|
|
155
|
-
grid-template-columns: repeat(auto-fit, minmax(${minWidth}, 1fr));
|
|
156
|
-
gap: ${gap};
|
|
157
|
-
`;
|
|
158
|
-
} else {
|
|
159
|
-
// For vertical grid, stack rows
|
|
160
|
-
containerEl.style.cssText = `
|
|
161
|
-
display: grid;
|
|
162
|
-
grid-template-columns: 1fr;
|
|
163
|
-
grid-auto-rows: auto;
|
|
164
|
-
gap: ${gap};
|
|
165
|
-
`;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
111
|
/* -------------------------
|
|
170
112
|
* Render
|
|
171
113
|
* ------------------------- */
|
|
172
114
|
|
|
173
|
-
|
|
115
|
+
render(targetId?: string | HTMLElement): this {
|
|
174
116
|
let container: HTMLElement;
|
|
175
|
-
|
|
117
|
+
|
|
176
118
|
if (targetId) {
|
|
177
|
-
// If targetId is an HTMLElement (passed from parent container), use it directly
|
|
178
119
|
if (targetId instanceof HTMLElement) {
|
|
179
120
|
container = targetId;
|
|
180
121
|
} else {
|
|
181
|
-
// Otherwise query for it
|
|
182
122
|
const target = document.querySelector(targetId);
|
|
183
123
|
if (!target || !(target instanceof HTMLElement)) {
|
|
184
124
|
throw new Error(`Container: Target element "${targetId}" not found`);
|
|
@@ -186,97 +126,53 @@ export class Container extends Reactive {
|
|
|
186
126
|
container = target;
|
|
187
127
|
}
|
|
188
128
|
} else {
|
|
189
|
-
container = getOrCreateContainer(this.
|
|
129
|
+
container = getOrCreateContainer(this._id);
|
|
190
130
|
}
|
|
191
|
-
|
|
131
|
+
|
|
192
132
|
this.container = container;
|
|
193
|
-
const {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
textWrapper.className = 'jux-container-item';
|
|
215
|
-
textWrapper.style.cssText = 'padding: 1rem; display: flex; align-items: center; justify-content: center; font-weight: bold;';
|
|
216
|
-
textWrapper.textContent = child;
|
|
217
|
-
container.appendChild(textWrapper);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Get child ID
|
|
222
|
-
let childId: string | null = null;
|
|
223
|
-
if ((child as any).id) {
|
|
224
|
-
childId = (child as any).id;
|
|
225
|
-
} else if ((child as any)._componentId) {
|
|
226
|
-
childId = (child as any)._componentId;
|
|
227
|
-
} else {
|
|
228
|
-
childId = `${this._componentId}-child-${i}`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Create wrapper for child
|
|
232
|
-
const childWrapper = document.createElement('div');
|
|
233
|
-
childWrapper.className = 'jux-container-item';
|
|
234
|
-
childWrapper.id = `${childId}-wrapper`;
|
|
235
|
-
container.appendChild(childWrapper);
|
|
236
|
-
|
|
237
|
-
// Render child INTO the wrapper (not to its own container)
|
|
238
|
-
if (typeof (child as any).render === 'function') {
|
|
239
|
-
try {
|
|
240
|
-
// Pass the wrapper element directly, not a selector
|
|
241
|
-
const result = (child as any).render(childWrapper);
|
|
242
|
-
if (result && typeof (result as any).then === 'function') {
|
|
243
|
-
await result;
|
|
244
|
-
}
|
|
245
|
-
} catch (err) {
|
|
246
|
-
console.error(`Container: Error rendering child ${i}:`, err);
|
|
247
|
-
childWrapper.innerHTML = `<div style="color: #ff6b6b; padding: 1rem;">Error: ${(err as Error).message}</div>`;
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
// If no render method, try to append directly
|
|
251
|
-
if (child instanceof HTMLElement) {
|
|
252
|
-
childWrapper.appendChild(child);
|
|
253
|
-
}
|
|
133
|
+
const { class: className, style, direction, gap, align, justify } = this.state;
|
|
134
|
+
|
|
135
|
+
const div = document.createElement('div');
|
|
136
|
+
div.id = this._id;
|
|
137
|
+
|
|
138
|
+
// Always include jux-container class, append custom classes
|
|
139
|
+
div.className = className ? `jux-container ${className}` : 'jux-container';
|
|
140
|
+
|
|
141
|
+
// Only apply flex styles if any flex properties are set
|
|
142
|
+
const usesFlexbox = direction || gap !== undefined || align || justify;
|
|
143
|
+
|
|
144
|
+
let computedStyle = style;
|
|
145
|
+
|
|
146
|
+
if (usesFlexbox) {
|
|
147
|
+
const flexStyles: string[] = ['display: flex', 'width: max-content'];
|
|
148
|
+
|
|
149
|
+
if (direction) flexStyles.push(`flex-direction: ${direction}`);
|
|
150
|
+
|
|
151
|
+
if (gap !== undefined) {
|
|
152
|
+
const gapValue = typeof gap === 'number' ? `${gap}px` : gap;
|
|
153
|
+
flexStyles.push(`gap: ${gapValue}`);
|
|
254
154
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
155
|
+
// Only set align-items if explicitly specified
|
|
156
|
+
if (align) flexStyles.push(`align-items: ${align}`);
|
|
157
|
+
// Only set justify-content if explicitly specified
|
|
158
|
+
if (justify) flexStyles.push(`justify-content: ${justify}`);
|
|
260
159
|
|
|
261
|
-
|
|
262
|
-
* Render to another Jux component's container
|
|
263
|
-
*/
|
|
264
|
-
async renderTo(juxComponent: any): Promise<this> {
|
|
265
|
-
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
266
|
-
throw new Error('Container.renderTo: Invalid component - not an object');
|
|
160
|
+
computedStyle = `${flexStyles.join('; ')}; ${style}`.trim();
|
|
267
161
|
}
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
162
|
+
|
|
163
|
+
if (computedStyle) {
|
|
164
|
+
div.setAttribute('style', computedStyle);
|
|
271
165
|
}
|
|
272
|
-
|
|
273
|
-
|
|
166
|
+
|
|
167
|
+
container.appendChild(div);
|
|
168
|
+
|
|
169
|
+
return this;
|
|
274
170
|
}
|
|
275
171
|
}
|
|
276
172
|
|
|
277
173
|
/**
|
|
278
174
|
* Factory helper
|
|
279
175
|
*/
|
|
280
|
-
export function container(
|
|
281
|
-
return new Container(
|
|
176
|
+
export function container(id: string, options: ContainerOptions = {}): Container {
|
|
177
|
+
return new Container(id, options);
|
|
282
178
|
}
|
package/lib/components/data.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Reactive, getOrCreateContainer } from './reactivity.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Data component - SQL query execution
|
|
5
|
-
* Note: No
|
|
3
|
+
* Note: No id needed - this is a data-only component
|
|
6
4
|
*
|
|
7
5
|
* Usage:
|
|
8
6
|
* const posts = jux.data('SELECT * FROM posts WHERE id = ?', [1]);
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DatePicker component options
|
|
6
|
+
*/
|
|
7
|
+
export interface DatePickerOptions {
|
|
8
|
+
value?: string;
|
|
9
|
+
min?: string;
|
|
10
|
+
max?: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
name?: string;
|
|
14
|
+
onChange?: (value: string) => void;
|
|
15
|
+
style?: string;
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* DatePicker component state
|
|
21
|
+
*/
|
|
22
|
+
type DatePickerState = {
|
|
23
|
+
value: string;
|
|
24
|
+
min: string;
|
|
25
|
+
max: string;
|
|
26
|
+
placeholder: string;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
name: string;
|
|
29
|
+
style: string;
|
|
30
|
+
class: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* DatePicker component - Calendar input
|
|
35
|
+
*
|
|
36
|
+
* Usage:
|
|
37
|
+
* jux.datepicker('start-date', {
|
|
38
|
+
* placeholder: 'Select date',
|
|
39
|
+
* value: '2024-01-15',
|
|
40
|
+
* onChange: (date) => console.log(date)
|
|
41
|
+
* }).render('#form');
|
|
42
|
+
*
|
|
43
|
+
* // Two-way binding
|
|
44
|
+
* const dateState = state('2024-01-15');
|
|
45
|
+
* jux.datepicker('date').bind(dateState).render('#form');
|
|
46
|
+
*/
|
|
47
|
+
export class DatePicker {
|
|
48
|
+
state: DatePickerState;
|
|
49
|
+
container: HTMLElement | null = null;
|
|
50
|
+
_id: string;
|
|
51
|
+
id: string;
|
|
52
|
+
private _onChange?: (value: string) => void;
|
|
53
|
+
private _boundState?: State<string>;
|
|
54
|
+
|
|
55
|
+
constructor(id: string, options: DatePickerOptions = {}) {
|
|
56
|
+
this._id = id;
|
|
57
|
+
this.id = id;
|
|
58
|
+
this._onChange = options.onChange;
|
|
59
|
+
|
|
60
|
+
this.state = {
|
|
61
|
+
value: options.value ?? '',
|
|
62
|
+
min: options.min ?? '',
|
|
63
|
+
max: options.max ?? '',
|
|
64
|
+
placeholder: options.placeholder ?? 'Select date',
|
|
65
|
+
disabled: options.disabled ?? false,
|
|
66
|
+
name: options.name ?? id,
|
|
67
|
+
style: options.style ?? '',
|
|
68
|
+
class: options.class ?? ''
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* -------------------------
|
|
73
|
+
* Fluent API
|
|
74
|
+
* ------------------------- */
|
|
75
|
+
|
|
76
|
+
value(value: string): this {
|
|
77
|
+
this.state.value = value;
|
|
78
|
+
this._updateElement();
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
min(value: string): this {
|
|
83
|
+
this.state.min = value;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
max(value: string): this {
|
|
88
|
+
this.state.max = value;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
placeholder(value: string): this {
|
|
93
|
+
this.state.placeholder = value;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
disabled(value: boolean): this {
|
|
98
|
+
this.state.disabled = value;
|
|
99
|
+
this._updateElement();
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
name(value: string): this {
|
|
104
|
+
this.state.name = value;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
style(value: string): this {
|
|
109
|
+
this.state.style = value;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class(value: string): this {
|
|
114
|
+
this.state.class = value;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onChange(handler: (value: string) => void): this {
|
|
119
|
+
this._onChange = handler;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Two-way binding to state
|
|
125
|
+
*/
|
|
126
|
+
bind(stateObj: State<string>): this {
|
|
127
|
+
this._boundState = stateObj;
|
|
128
|
+
|
|
129
|
+
stateObj.subscribe((val) => {
|
|
130
|
+
this.state.value = val;
|
|
131
|
+
this._updateElement();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.onChange((value) => stateObj.set(value));
|
|
135
|
+
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* -------------------------
|
|
140
|
+
* Helpers
|
|
141
|
+
* ------------------------- */
|
|
142
|
+
|
|
143
|
+
private _updateElement(): void {
|
|
144
|
+
const input = document.getElementById(`${this._id}-input`) as HTMLInputElement;
|
|
145
|
+
if (input) {
|
|
146
|
+
input.value = this.state.value;
|
|
147
|
+
input.disabled = this.state.disabled;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
getValue(): string {
|
|
152
|
+
return this.state.value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
getDate(): Date | null {
|
|
156
|
+
return this.state.value ? new Date(this.state.value) : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* -------------------------
|
|
160
|
+
* Render
|
|
161
|
+
* ------------------------- */
|
|
162
|
+
|
|
163
|
+
render(targetId?: string): this {
|
|
164
|
+
let container: HTMLElement;
|
|
165
|
+
|
|
166
|
+
if (targetId) {
|
|
167
|
+
const target = document.querySelector(targetId);
|
|
168
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
169
|
+
throw new Error(`DatePicker: Target element "${targetId}" not found`);
|
|
170
|
+
}
|
|
171
|
+
container = target;
|
|
172
|
+
} else {
|
|
173
|
+
container = getOrCreateContainer(this._id);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.container = container;
|
|
177
|
+
const { value, min, max, placeholder, disabled, name, style, class: className } = this.state;
|
|
178
|
+
|
|
179
|
+
const wrapper = document.createElement('div');
|
|
180
|
+
wrapper.className = 'jux-datepicker';
|
|
181
|
+
wrapper.id = this._id;
|
|
182
|
+
|
|
183
|
+
if (className) {
|
|
184
|
+
wrapper.className += ` ${className}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (style) {
|
|
188
|
+
wrapper.setAttribute('style', style);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const input = document.createElement('input');
|
|
192
|
+
input.type = 'date';
|
|
193
|
+
input.className = 'jux-datepicker-input';
|
|
194
|
+
input.id = `${this._id}-input`;
|
|
195
|
+
input.name = name;
|
|
196
|
+
input.value = value;
|
|
197
|
+
input.disabled = disabled;
|
|
198
|
+
|
|
199
|
+
if (min) input.min = min;
|
|
200
|
+
if (max) input.max = max;
|
|
201
|
+
if (placeholder) input.setAttribute('placeholder', placeholder);
|
|
202
|
+
|
|
203
|
+
input.addEventListener('change', (e) => {
|
|
204
|
+
const target = e.target as HTMLInputElement;
|
|
205
|
+
this.state.value = target.value;
|
|
206
|
+
if (this._onChange) {
|
|
207
|
+
this._onChange(target.value);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
wrapper.appendChild(input);
|
|
212
|
+
container.appendChild(wrapper);
|
|
213
|
+
return this;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
renderTo(juxComponent: any): this {
|
|
217
|
+
if (!juxComponent?._id) {
|
|
218
|
+
throw new Error('DatePicker.renderTo: Invalid component');
|
|
219
|
+
}
|
|
220
|
+
return this.render(`#${juxComponent._id}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function datepicker(id: string, options: DatePickerOptions = {}): DatePicker {
|
|
225
|
+
return new DatePicker(id, options);
|
|
226
|
+
}
|