@portel/photon-core 2.3.0 → 2.5.0
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/dist/asset-discovery.d.ts +25 -0
- package/dist/asset-discovery.d.ts.map +1 -0
- package/dist/asset-discovery.js +145 -0
- package/dist/asset-discovery.js.map +1 -0
- package/dist/base.d.ts +6 -0
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +11 -1
- package/dist/base.js.map +1 -1
- package/dist/class-detection.d.ts +32 -0
- package/dist/class-detection.d.ts.map +1 -0
- package/dist/class-detection.js +86 -0
- package/dist/class-detection.js.map +1 -0
- package/dist/collections/ReactiveArray.d.ts +97 -0
- package/dist/collections/ReactiveArray.d.ts.map +1 -0
- package/dist/collections/ReactiveArray.js +158 -0
- package/dist/collections/ReactiveArray.js.map +1 -0
- package/dist/collections/ReactiveMap.d.ts +50 -0
- package/dist/collections/ReactiveMap.d.ts.map +1 -0
- package/dist/collections/ReactiveMap.js +71 -0
- package/dist/collections/ReactiveMap.js.map +1 -0
- package/dist/collections/ReactiveSet.d.ts +50 -0
- package/dist/collections/ReactiveSet.d.ts.map +1 -0
- package/dist/collections/ReactiveSet.js +71 -0
- package/dist/collections/ReactiveSet.js.map +1 -0
- package/dist/collections/index.d.ts +44 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +44 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/compiler.d.ts +22 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +48 -0
- package/dist/compiler.js.map +1 -0
- package/dist/env-utils.d.ts +61 -0
- package/dist/env-utils.d.ts.map +1 -0
- package/dist/env-utils.js +171 -0
- package/dist/env-utils.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -1
- package/dist/mime-types.d.ts +13 -0
- package/dist/mime-types.d.ts.map +1 -0
- package/dist/mime-types.js +47 -0
- package/dist/mime-types.js.map +1 -0
- package/dist/rendering/index.d.ts +49 -0
- package/dist/rendering/index.d.ts.map +1 -1
- package/dist/rendering/index.js +153 -0
- package/dist/rendering/index.js.map +1 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +3 -0
- package/dist/schema-extractor.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui-types/Cards.d.ts +139 -0
- package/dist/ui-types/Cards.d.ts.map +1 -0
- package/dist/ui-types/Cards.js +235 -0
- package/dist/ui-types/Cards.js.map +1 -0
- package/dist/ui-types/Chart.d.ts +136 -0
- package/dist/ui-types/Chart.d.ts.map +1 -0
- package/dist/ui-types/Chart.js +188 -0
- package/dist/ui-types/Chart.js.map +1 -0
- package/dist/ui-types/Field.d.ts +342 -0
- package/dist/ui-types/Field.d.ts.map +1 -0
- package/dist/ui-types/Field.js +200 -0
- package/dist/ui-types/Field.js.map +1 -0
- package/dist/ui-types/FieldRenderer.d.ts +32 -0
- package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
- package/dist/ui-types/FieldRenderer.js +277 -0
- package/dist/ui-types/FieldRenderer.js.map +1 -0
- package/dist/ui-types/Form.d.ts +212 -0
- package/dist/ui-types/Form.d.ts.map +1 -0
- package/dist/ui-types/Form.js +278 -0
- package/dist/ui-types/Form.js.map +1 -0
- package/dist/ui-types/Progress.d.ts +130 -0
- package/dist/ui-types/Progress.d.ts.map +1 -0
- package/dist/ui-types/Progress.js +191 -0
- package/dist/ui-types/Progress.js.map +1 -0
- package/dist/ui-types/Stats.d.ts +108 -0
- package/dist/ui-types/Stats.d.ts.map +1 -0
- package/dist/ui-types/Stats.js +162 -0
- package/dist/ui-types/Stats.js.map +1 -0
- package/dist/ui-types/Table.d.ts +206 -0
- package/dist/ui-types/Table.d.ts.map +1 -0
- package/dist/ui-types/Table.js +367 -0
- package/dist/ui-types/Table.js.map +1 -0
- package/dist/ui-types/base.d.ts +17 -0
- package/dist/ui-types/base.d.ts.map +1 -0
- package/dist/ui-types/base.js +18 -0
- package/dist/ui-types/base.js.map +1 -0
- package/dist/ui-types/index.d.ts +42 -0
- package/dist/ui-types/index.d.ts.map +1 -0
- package/dist/ui-types/index.js +50 -0
- package/dist/ui-types/index.js.map +1 -0
- package/dist/validation.d.ts +51 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +249 -0
- package/dist/validation.js.map +1 -0
- package/dist/version-check.d.ts +22 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +91 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +2 -2
- package/src/asset-discovery.ts +161 -0
- package/src/base.ts +13 -1
- package/src/class-detection.ts +94 -0
- package/src/collections/ReactiveArray.ts +179 -0
- package/src/collections/ReactiveMap.ts +81 -0
- package/src/collections/ReactiveSet.ts +81 -0
- package/src/collections/index.ts +44 -0
- package/src/compiler.ts +57 -0
- package/src/env-utils.ts +216 -0
- package/src/index.ts +155 -0
- package/src/mime-types.ts +49 -0
- package/src/rendering/index.ts +197 -0
- package/src/schema-extractor.ts +4 -0
- package/src/types.ts +4 -0
- package/src/ui-types/Cards.ts +286 -0
- package/src/ui-types/Chart.ts +239 -0
- package/src/ui-types/Field.ts +594 -0
- package/src/ui-types/FieldRenderer.ts +364 -0
- package/src/ui-types/Form.ts +363 -0
- package/src/ui-types/Progress.ts +237 -0
- package/src/ui-types/Stats.ts +204 -0
- package/src/ui-types/Table.ts +438 -0
- package/src/ui-types/base.ts +25 -0
- package/src/ui-types/index.ts +96 -0
- package/src/ui-types/ui-types.test.ts +444 -0
- package/src/validation.ts +363 -0
- package/src/version-check.ts +92 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form - Purpose-driven type for interactive forms
|
|
3
|
+
*
|
|
4
|
+
* Automatically renders as a form that submits back to the photon.
|
|
5
|
+
* Unlike io.ask.form (which is for elicitation during method execution),
|
|
6
|
+
* Form is a return type that creates a persistent interactive form.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* async settings() {
|
|
11
|
+
* return new Form()
|
|
12
|
+
* .title('User Settings')
|
|
13
|
+
* .text('name', 'Display Name', { required: true })
|
|
14
|
+
* .email('email', 'Email Address')
|
|
15
|
+
* .select('theme', 'Theme', ['light', 'dark', 'auto'])
|
|
16
|
+
* .toggle('notifications', 'Enable Notifications')
|
|
17
|
+
* .submit('Save Settings', 'saveSettings');
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* async saveSettings(params: { name: string; email: string; theme: string; notifications: boolean }) {
|
|
21
|
+
* // Handle form submission
|
|
22
|
+
* return { success: true };
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { PhotonUIType } from './base.js';
|
|
28
|
+
|
|
29
|
+
export type FieldType =
|
|
30
|
+
| 'text'
|
|
31
|
+
| 'email'
|
|
32
|
+
| 'password'
|
|
33
|
+
| 'number'
|
|
34
|
+
| 'textarea'
|
|
35
|
+
| 'select'
|
|
36
|
+
| 'multiselect'
|
|
37
|
+
| 'toggle'
|
|
38
|
+
| 'checkbox'
|
|
39
|
+
| 'radio'
|
|
40
|
+
| 'date'
|
|
41
|
+
| 'time'
|
|
42
|
+
| 'datetime'
|
|
43
|
+
| 'file'
|
|
44
|
+
| 'color'
|
|
45
|
+
| 'range'
|
|
46
|
+
| 'hidden';
|
|
47
|
+
|
|
48
|
+
export interface FormField {
|
|
49
|
+
name: string;
|
|
50
|
+
type: FieldType;
|
|
51
|
+
label?: string;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
defaultValue?: any;
|
|
54
|
+
required?: boolean;
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
options?: Array<string | { label: string; value: any }>;
|
|
57
|
+
min?: number;
|
|
58
|
+
max?: number;
|
|
59
|
+
step?: number;
|
|
60
|
+
rows?: number;
|
|
61
|
+
accept?: string; // For file inputs
|
|
62
|
+
pattern?: string;
|
|
63
|
+
helpText?: string;
|
|
64
|
+
group?: string; // For grouping fields
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface FormOptions {
|
|
68
|
+
title?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
submitLabel?: string;
|
|
71
|
+
submitMethod?: string; // Photon method to call on submit
|
|
72
|
+
cancelLabel?: string;
|
|
73
|
+
layout?: 'vertical' | 'horizontal' | 'inline';
|
|
74
|
+
columns?: 1 | 2 | 3;
|
|
75
|
+
showReset?: boolean;
|
|
76
|
+
confirmSubmit?: string; // Confirmation message before submit
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class Form extends PhotonUIType {
|
|
80
|
+
readonly _photonType = 'form' as const;
|
|
81
|
+
|
|
82
|
+
private _fields: FormField[] = [];
|
|
83
|
+
private _options: FormOptions = {
|
|
84
|
+
layout: 'vertical',
|
|
85
|
+
columns: 1,
|
|
86
|
+
submitLabel: 'Submit',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new Form
|
|
91
|
+
*/
|
|
92
|
+
constructor() {
|
|
93
|
+
super();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set form title
|
|
98
|
+
*/
|
|
99
|
+
title(title: string): this {
|
|
100
|
+
this._options.title = title;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Set form description
|
|
106
|
+
*/
|
|
107
|
+
description(text: string): this {
|
|
108
|
+
this._options.description = text;
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Add a generic field
|
|
114
|
+
*/
|
|
115
|
+
field(name: string, type: FieldType, label?: string, options?: Partial<FormField>): this {
|
|
116
|
+
this._fields.push({
|
|
117
|
+
name,
|
|
118
|
+
type,
|
|
119
|
+
label: label ?? this._formatLabel(name),
|
|
120
|
+
...options,
|
|
121
|
+
});
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Add a text input
|
|
127
|
+
*/
|
|
128
|
+
text(name: string, label?: string, options?: Partial<FormField>): this {
|
|
129
|
+
return this.field(name, 'text', label, options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add an email input
|
|
134
|
+
*/
|
|
135
|
+
email(name: string, label?: string, options?: Partial<FormField>): this {
|
|
136
|
+
return this.field(name, 'email', label, options);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Add a password input
|
|
141
|
+
*/
|
|
142
|
+
password(name: string, label?: string, options?: Partial<FormField>): this {
|
|
143
|
+
return this.field(name, 'password', label, options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Add a number input
|
|
148
|
+
*/
|
|
149
|
+
number(name: string, label?: string, options?: Partial<FormField> & { min?: number; max?: number; step?: number }): this {
|
|
150
|
+
return this.field(name, 'number', label, options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Add a textarea
|
|
155
|
+
*/
|
|
156
|
+
textarea(name: string, label?: string, options?: Partial<FormField> & { rows?: number }): this {
|
|
157
|
+
return this.field(name, 'textarea', label, { rows: 4, ...options });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Add a select dropdown
|
|
162
|
+
*/
|
|
163
|
+
select(name: string, label: string | undefined, choices: Array<string | { label: string; value: any }>, options?: Partial<FormField>): this {
|
|
164
|
+
return this.field(name, 'select', label, { options: choices, ...options });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Add a multi-select
|
|
169
|
+
*/
|
|
170
|
+
multiselect(name: string, label: string | undefined, choices: Array<string | { label: string; value: any }>, options?: Partial<FormField>): this {
|
|
171
|
+
return this.field(name, 'multiselect', label, { options: choices, ...options });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Add a toggle switch
|
|
176
|
+
*/
|
|
177
|
+
toggle(name: string, label?: string, options?: Partial<FormField>): this {
|
|
178
|
+
return this.field(name, 'toggle', label, options);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Add a checkbox
|
|
183
|
+
*/
|
|
184
|
+
checkbox(name: string, label?: string, options?: Partial<FormField>): this {
|
|
185
|
+
return this.field(name, 'checkbox', label, options);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Add radio buttons
|
|
190
|
+
*/
|
|
191
|
+
radio(name: string, label: string | undefined, choices: Array<string | { label: string; value: any }>, options?: Partial<FormField>): this {
|
|
192
|
+
return this.field(name, 'radio', label, { options: choices, ...options });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Add a date picker
|
|
197
|
+
*/
|
|
198
|
+
date(name: string, label?: string, options?: Partial<FormField>): this {
|
|
199
|
+
return this.field(name, 'date', label, options);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Add a time picker
|
|
204
|
+
*/
|
|
205
|
+
time(name: string, label?: string, options?: Partial<FormField>): this {
|
|
206
|
+
return this.field(name, 'time', label, options);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Add a datetime picker
|
|
211
|
+
*/
|
|
212
|
+
datetime(name: string, label?: string, options?: Partial<FormField>): this {
|
|
213
|
+
return this.field(name, 'datetime', label, options);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Add a file upload
|
|
218
|
+
*/
|
|
219
|
+
file(name: string, label?: string, options?: Partial<FormField> & { accept?: string }): this {
|
|
220
|
+
return this.field(name, 'file', label, options);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Add a color picker
|
|
225
|
+
*/
|
|
226
|
+
color(name: string, label?: string, options?: Partial<FormField>): this {
|
|
227
|
+
return this.field(name, 'color', label, options);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Add a range slider
|
|
232
|
+
*/
|
|
233
|
+
range(name: string, label?: string, options?: Partial<FormField> & { min?: number; max?: number; step?: number }): this {
|
|
234
|
+
return this.field(name, 'range', label, { min: 0, max: 100, step: 1, ...options });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Add a hidden field
|
|
239
|
+
*/
|
|
240
|
+
hidden(name: string, value: any): this {
|
|
241
|
+
return this.field(name, 'hidden', undefined, { defaultValue: value });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Configure submit button
|
|
246
|
+
*/
|
|
247
|
+
submit(label: string, method?: string): this {
|
|
248
|
+
this._options.submitLabel = label;
|
|
249
|
+
if (method) this._options.submitMethod = method;
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Add cancel button
|
|
255
|
+
*/
|
|
256
|
+
cancel(label: string = 'Cancel'): this {
|
|
257
|
+
this._options.cancelLabel = label;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Set form layout
|
|
263
|
+
*/
|
|
264
|
+
layout(type: 'vertical' | 'horizontal' | 'inline'): this {
|
|
265
|
+
this._options.layout = type;
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Set number of columns
|
|
271
|
+
*/
|
|
272
|
+
columns(count: 1 | 2 | 3): this {
|
|
273
|
+
this._options.columns = count;
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Show reset button
|
|
279
|
+
*/
|
|
280
|
+
showReset(enabled: boolean = true): this {
|
|
281
|
+
this._options.showReset = enabled;
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Require confirmation before submit
|
|
287
|
+
*/
|
|
288
|
+
confirmSubmit(message: string): this {
|
|
289
|
+
this._options.confirmSubmit = message;
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Group subsequent fields
|
|
295
|
+
*/
|
|
296
|
+
group(name: string): this {
|
|
297
|
+
// Mark for next fields
|
|
298
|
+
// Implementation note: fields added after this call get this group
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Format field name to label
|
|
304
|
+
*/
|
|
305
|
+
private _formatLabel(name: string): string {
|
|
306
|
+
return name
|
|
307
|
+
.replace(/([A-Z])/g, ' $1')
|
|
308
|
+
.replace(/[_-]/g, ' ')
|
|
309
|
+
.replace(/^\w/, c => c.toUpperCase())
|
|
310
|
+
.trim();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
toJSON() {
|
|
314
|
+
return {
|
|
315
|
+
_photonType: this._photonType,
|
|
316
|
+
fields: this._fields,
|
|
317
|
+
options: this._options,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Render as plain text for MCP clients
|
|
323
|
+
* Shows the form structure (fields are not interactive in plain text)
|
|
324
|
+
*/
|
|
325
|
+
toString(): string {
|
|
326
|
+
const lines: string[] = [];
|
|
327
|
+
|
|
328
|
+
if (this._options.title) {
|
|
329
|
+
lines.push(`## ${this._options.title}`);
|
|
330
|
+
}
|
|
331
|
+
if (this._options.description) {
|
|
332
|
+
lines.push(this._options.description);
|
|
333
|
+
}
|
|
334
|
+
if (this._options.title || this._options.description) {
|
|
335
|
+
lines.push('');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
lines.push('**Form Fields:**');
|
|
339
|
+
for (const field of this._fields) {
|
|
340
|
+
if (field.type === 'hidden') continue;
|
|
341
|
+
|
|
342
|
+
let line = `- ${field.label ?? field.name}`;
|
|
343
|
+
if (field.required) line += ' (required)';
|
|
344
|
+
|
|
345
|
+
// Show options for select/radio
|
|
346
|
+
if (field.options && field.options.length > 0) {
|
|
347
|
+
const opts = field.options.map(o => typeof o === 'string' ? o : o.label);
|
|
348
|
+
line += `: [${opts.join(', ')}]`;
|
|
349
|
+
} else {
|
|
350
|
+
line += `: (${field.type})`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (field.helpText) line += ` - ${field.helpText}`;
|
|
354
|
+
lines.push(line);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (this._options.submitLabel) {
|
|
358
|
+
lines.push('', `[${this._options.submitLabel}]`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return lines.join('\n');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress - Purpose-driven type for progress indicators
|
|
3
|
+
*
|
|
4
|
+
* Automatically renders as progress bars or steps.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // Simple progress bar
|
|
9
|
+
* async uploadStatus() {
|
|
10
|
+
* return new Progress(75)
|
|
11
|
+
* .label('Uploading files...')
|
|
12
|
+
* .color('blue');
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* // Multiple progress bars
|
|
16
|
+
* async projectStatus() {
|
|
17
|
+
* return new Progress()
|
|
18
|
+
* .bar('Design', 100, { color: 'green' })
|
|
19
|
+
* .bar('Development', 65, { color: 'blue' })
|
|
20
|
+
* .bar('Testing', 20, { color: 'yellow' });
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* // Step indicator
|
|
24
|
+
* async checkoutSteps() {
|
|
25
|
+
* return new Progress('steps')
|
|
26
|
+
* .step('Cart', 'completed')
|
|
27
|
+
* .step('Shipping', 'current')
|
|
28
|
+
* .step('Payment', 'pending')
|
|
29
|
+
* .step('Confirm', 'pending');
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { PhotonUIType } from './base.js';
|
|
35
|
+
|
|
36
|
+
export type ProgressStyle = 'bar' | 'steps' | 'circle';
|
|
37
|
+
export type StepStatus = 'pending' | 'current' | 'completed' | 'error';
|
|
38
|
+
|
|
39
|
+
export interface ProgressBar {
|
|
40
|
+
label: string;
|
|
41
|
+
value: number;
|
|
42
|
+
max?: number;
|
|
43
|
+
color?: string;
|
|
44
|
+
showValue?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProgressStep {
|
|
48
|
+
label: string;
|
|
49
|
+
status: StepStatus;
|
|
50
|
+
description?: string;
|
|
51
|
+
icon?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ProgressOptions {
|
|
55
|
+
title?: string;
|
|
56
|
+
style?: ProgressStyle;
|
|
57
|
+
color?: string;
|
|
58
|
+
size?: 'sm' | 'md' | 'lg';
|
|
59
|
+
striped?: boolean;
|
|
60
|
+
animated?: boolean;
|
|
61
|
+
showValue?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class Progress extends PhotonUIType {
|
|
65
|
+
readonly _photonType = 'progress' as const;
|
|
66
|
+
|
|
67
|
+
private _bars: ProgressBar[] = [];
|
|
68
|
+
private _steps: ProgressStep[] = [];
|
|
69
|
+
private _value: number = 0;
|
|
70
|
+
private _max: number = 100;
|
|
71
|
+
private _options: ProgressOptions = {
|
|
72
|
+
style: 'bar',
|
|
73
|
+
size: 'md',
|
|
74
|
+
showValue: true,
|
|
75
|
+
animated: true,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a new Progress indicator
|
|
80
|
+
* @param valueOrStyle Initial value (0-100) or style ('steps', 'circle')
|
|
81
|
+
*/
|
|
82
|
+
constructor(valueOrStyle?: number | ProgressStyle) {
|
|
83
|
+
super();
|
|
84
|
+
if (typeof valueOrStyle === 'number') {
|
|
85
|
+
this._value = valueOrStyle;
|
|
86
|
+
} else if (valueOrStyle) {
|
|
87
|
+
this._options.style = valueOrStyle;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Set progress value (0-100)
|
|
93
|
+
*/
|
|
94
|
+
value(val: number, max?: number): this {
|
|
95
|
+
this._value = val;
|
|
96
|
+
if (max !== undefined) this._max = max;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set label
|
|
102
|
+
*/
|
|
103
|
+
label(text: string): this {
|
|
104
|
+
this._options.title = text;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Add a progress bar (for multi-bar display)
|
|
110
|
+
*/
|
|
111
|
+
bar(label: string, value: number, options?: { max?: number; color?: string; showValue?: boolean }): this {
|
|
112
|
+
this._bars.push({
|
|
113
|
+
label,
|
|
114
|
+
value,
|
|
115
|
+
max: options?.max ?? 100,
|
|
116
|
+
color: options?.color,
|
|
117
|
+
showValue: options?.showValue ?? true,
|
|
118
|
+
});
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Add a step (for step indicator)
|
|
124
|
+
*/
|
|
125
|
+
step(label: string, status: StepStatus = 'pending', options?: { description?: string; icon?: string }): this {
|
|
126
|
+
this._options.style = 'steps';
|
|
127
|
+
this._steps.push({
|
|
128
|
+
label,
|
|
129
|
+
status,
|
|
130
|
+
...options,
|
|
131
|
+
});
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Set color
|
|
137
|
+
*/
|
|
138
|
+
color(color: string): this {
|
|
139
|
+
this._options.color = color;
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Set size
|
|
145
|
+
*/
|
|
146
|
+
size(size: 'sm' | 'md' | 'lg'): this {
|
|
147
|
+
this._options.size = size;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Use striped style
|
|
153
|
+
*/
|
|
154
|
+
striped(enabled: boolean = true): this {
|
|
155
|
+
this._options.striped = enabled;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Enable animation
|
|
161
|
+
*/
|
|
162
|
+
animated(enabled: boolean = true): this {
|
|
163
|
+
this._options.animated = enabled;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Show/hide value text
|
|
169
|
+
*/
|
|
170
|
+
showValue(enabled: boolean = true): this {
|
|
171
|
+
this._options.showValue = enabled;
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Use circle style
|
|
177
|
+
*/
|
|
178
|
+
circle(): this {
|
|
179
|
+
this._options.style = 'circle';
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
toJSON() {
|
|
184
|
+
return {
|
|
185
|
+
_photonType: this._photonType,
|
|
186
|
+
value: this._value,
|
|
187
|
+
max: this._max,
|
|
188
|
+
bars: this._bars,
|
|
189
|
+
steps: this._steps,
|
|
190
|
+
options: this._options,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Render as plain text for MCP clients
|
|
196
|
+
*/
|
|
197
|
+
toString(): string {
|
|
198
|
+
const lines: string[] = [];
|
|
199
|
+
|
|
200
|
+
if (this._options.title) {
|
|
201
|
+
lines.push(this._options.title);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Steps display
|
|
205
|
+
if (this._steps.length > 0) {
|
|
206
|
+
const stepMarkers = this._steps.map(s => {
|
|
207
|
+
switch (s.status) {
|
|
208
|
+
case 'completed': return `[✓] ${s.label}`;
|
|
209
|
+
case 'current': return `[●] ${s.label}`;
|
|
210
|
+
case 'error': return `[✗] ${s.label}`;
|
|
211
|
+
default: return `[ ] ${s.label}`;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
lines.push(stepMarkers.join(' → '));
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Multiple bars
|
|
219
|
+
if (this._bars.length > 0) {
|
|
220
|
+
for (const bar of this._bars) {
|
|
221
|
+
const pct = Math.round((bar.value / (bar.max ?? 100)) * 100);
|
|
222
|
+
const filled = Math.round(pct / 5);
|
|
223
|
+
const barStr = '█'.repeat(filled) + '░'.repeat(20 - filled);
|
|
224
|
+
lines.push(`${bar.label}: [${barStr}] ${pct}%`);
|
|
225
|
+
}
|
|
226
|
+
return lines.join('\n');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Single progress bar
|
|
230
|
+
const pct = Math.round((this._value / this._max) * 100);
|
|
231
|
+
const filled = Math.round(pct / 5);
|
|
232
|
+
const barStr = '█'.repeat(filled) + '░'.repeat(20 - filled);
|
|
233
|
+
lines.push(`[${barStr}] ${pct}%`);
|
|
234
|
+
|
|
235
|
+
return lines.join('\n');
|
|
236
|
+
}
|
|
237
|
+
}
|