@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.
Files changed (130) hide show
  1. package/dist/asset-discovery.d.ts +25 -0
  2. package/dist/asset-discovery.d.ts.map +1 -0
  3. package/dist/asset-discovery.js +145 -0
  4. package/dist/asset-discovery.js.map +1 -0
  5. package/dist/base.d.ts +6 -0
  6. package/dist/base.d.ts.map +1 -1
  7. package/dist/base.js +11 -1
  8. package/dist/base.js.map +1 -1
  9. package/dist/class-detection.d.ts +32 -0
  10. package/dist/class-detection.d.ts.map +1 -0
  11. package/dist/class-detection.js +86 -0
  12. package/dist/class-detection.js.map +1 -0
  13. package/dist/collections/ReactiveArray.d.ts +97 -0
  14. package/dist/collections/ReactiveArray.d.ts.map +1 -0
  15. package/dist/collections/ReactiveArray.js +158 -0
  16. package/dist/collections/ReactiveArray.js.map +1 -0
  17. package/dist/collections/ReactiveMap.d.ts +50 -0
  18. package/dist/collections/ReactiveMap.d.ts.map +1 -0
  19. package/dist/collections/ReactiveMap.js +71 -0
  20. package/dist/collections/ReactiveMap.js.map +1 -0
  21. package/dist/collections/ReactiveSet.d.ts +50 -0
  22. package/dist/collections/ReactiveSet.d.ts.map +1 -0
  23. package/dist/collections/ReactiveSet.js +71 -0
  24. package/dist/collections/ReactiveSet.js.map +1 -0
  25. package/dist/collections/index.d.ts +44 -0
  26. package/dist/collections/index.d.ts.map +1 -0
  27. package/dist/collections/index.js +44 -0
  28. package/dist/collections/index.js.map +1 -0
  29. package/dist/compiler.d.ts +22 -0
  30. package/dist/compiler.d.ts.map +1 -0
  31. package/dist/compiler.js +48 -0
  32. package/dist/compiler.js.map +1 -0
  33. package/dist/env-utils.d.ts +61 -0
  34. package/dist/env-utils.d.ts.map +1 -0
  35. package/dist/env-utils.js +171 -0
  36. package/dist/env-utils.js.map +1 -0
  37. package/dist/index.d.ts +9 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +37 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/mime-types.d.ts +13 -0
  42. package/dist/mime-types.d.ts.map +1 -0
  43. package/dist/mime-types.js +47 -0
  44. package/dist/mime-types.js.map +1 -0
  45. package/dist/rendering/index.d.ts +49 -0
  46. package/dist/rendering/index.d.ts.map +1 -1
  47. package/dist/rendering/index.js +153 -0
  48. package/dist/rendering/index.js.map +1 -1
  49. package/dist/schema-extractor.d.ts.map +1 -1
  50. package/dist/schema-extractor.js +3 -0
  51. package/dist/schema-extractor.js.map +1 -1
  52. package/dist/types.d.ts +4 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js.map +1 -1
  55. package/dist/ui-types/Cards.d.ts +139 -0
  56. package/dist/ui-types/Cards.d.ts.map +1 -0
  57. package/dist/ui-types/Cards.js +235 -0
  58. package/dist/ui-types/Cards.js.map +1 -0
  59. package/dist/ui-types/Chart.d.ts +136 -0
  60. package/dist/ui-types/Chart.d.ts.map +1 -0
  61. package/dist/ui-types/Chart.js +188 -0
  62. package/dist/ui-types/Chart.js.map +1 -0
  63. package/dist/ui-types/Field.d.ts +342 -0
  64. package/dist/ui-types/Field.d.ts.map +1 -0
  65. package/dist/ui-types/Field.js +200 -0
  66. package/dist/ui-types/Field.js.map +1 -0
  67. package/dist/ui-types/FieldRenderer.d.ts +32 -0
  68. package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
  69. package/dist/ui-types/FieldRenderer.js +277 -0
  70. package/dist/ui-types/FieldRenderer.js.map +1 -0
  71. package/dist/ui-types/Form.d.ts +212 -0
  72. package/dist/ui-types/Form.d.ts.map +1 -0
  73. package/dist/ui-types/Form.js +278 -0
  74. package/dist/ui-types/Form.js.map +1 -0
  75. package/dist/ui-types/Progress.d.ts +130 -0
  76. package/dist/ui-types/Progress.d.ts.map +1 -0
  77. package/dist/ui-types/Progress.js +191 -0
  78. package/dist/ui-types/Progress.js.map +1 -0
  79. package/dist/ui-types/Stats.d.ts +108 -0
  80. package/dist/ui-types/Stats.d.ts.map +1 -0
  81. package/dist/ui-types/Stats.js +162 -0
  82. package/dist/ui-types/Stats.js.map +1 -0
  83. package/dist/ui-types/Table.d.ts +206 -0
  84. package/dist/ui-types/Table.d.ts.map +1 -0
  85. package/dist/ui-types/Table.js +367 -0
  86. package/dist/ui-types/Table.js.map +1 -0
  87. package/dist/ui-types/base.d.ts +17 -0
  88. package/dist/ui-types/base.d.ts.map +1 -0
  89. package/dist/ui-types/base.js +18 -0
  90. package/dist/ui-types/base.js.map +1 -0
  91. package/dist/ui-types/index.d.ts +42 -0
  92. package/dist/ui-types/index.d.ts.map +1 -0
  93. package/dist/ui-types/index.js +50 -0
  94. package/dist/ui-types/index.js.map +1 -0
  95. package/dist/validation.d.ts +51 -0
  96. package/dist/validation.d.ts.map +1 -0
  97. package/dist/validation.js +249 -0
  98. package/dist/validation.js.map +1 -0
  99. package/dist/version-check.d.ts +22 -0
  100. package/dist/version-check.d.ts.map +1 -0
  101. package/dist/version-check.js +91 -0
  102. package/dist/version-check.js.map +1 -0
  103. package/package.json +2 -2
  104. package/src/asset-discovery.ts +161 -0
  105. package/src/base.ts +13 -1
  106. package/src/class-detection.ts +94 -0
  107. package/src/collections/ReactiveArray.ts +179 -0
  108. package/src/collections/ReactiveMap.ts +81 -0
  109. package/src/collections/ReactiveSet.ts +81 -0
  110. package/src/collections/index.ts +44 -0
  111. package/src/compiler.ts +57 -0
  112. package/src/env-utils.ts +216 -0
  113. package/src/index.ts +155 -0
  114. package/src/mime-types.ts +49 -0
  115. package/src/rendering/index.ts +197 -0
  116. package/src/schema-extractor.ts +4 -0
  117. package/src/types.ts +4 -0
  118. package/src/ui-types/Cards.ts +286 -0
  119. package/src/ui-types/Chart.ts +239 -0
  120. package/src/ui-types/Field.ts +594 -0
  121. package/src/ui-types/FieldRenderer.ts +364 -0
  122. package/src/ui-types/Form.ts +363 -0
  123. package/src/ui-types/Progress.ts +237 -0
  124. package/src/ui-types/Stats.ts +204 -0
  125. package/src/ui-types/Table.ts +438 -0
  126. package/src/ui-types/base.ts +25 -0
  127. package/src/ui-types/index.ts +96 -0
  128. package/src/ui-types/ui-types.test.ts +444 -0
  129. package/src/validation.ts +363 -0
  130. package/src/version-check.ts +92 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Cards - Purpose-driven type for card-based layouts
3
+ *
4
+ * Automatically renders as a grid of cards with customizable fields.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * async products() {
9
+ * return new Cards()
10
+ * .title('Featured Products')
11
+ * .image('imageUrl')
12
+ * .heading('name')
13
+ * .subtitle('category')
14
+ * .description('summary')
15
+ * .badge('status')
16
+ * .items([
17
+ * { name: 'Widget', category: 'Tools', summary: '...', status: 'New', imageUrl: '...' },
18
+ * ]);
19
+ * }
20
+ * ```
21
+ */
22
+
23
+ import { PhotonUIType } from './base.js';
24
+
25
+ export interface CardFieldMapping {
26
+ image?: string;
27
+ heading?: string;
28
+ subtitle?: string;
29
+ description?: string;
30
+ badge?: string;
31
+ footer?: string;
32
+ link?: string;
33
+ meta?: string[];
34
+ }
35
+
36
+ export interface CardsOptions {
37
+ title?: string;
38
+ columns?: 1 | 2 | 3 | 4 | 6;
39
+ compact?: boolean;
40
+ clickable?: boolean;
41
+ hoverable?: boolean;
42
+ bordered?: boolean;
43
+ aspectRatio?: 'square' | 'video' | 'portrait' | 'auto';
44
+ }
45
+
46
+ export class Cards extends PhotonUIType {
47
+ readonly _photonType = 'cards' as const;
48
+
49
+ private _items: Record<string, any>[] = [];
50
+ private _fields: CardFieldMapping = {};
51
+ private _options: CardsOptions = {
52
+ columns: 3,
53
+ hoverable: true,
54
+ bordered: true,
55
+ aspectRatio: 'auto',
56
+ };
57
+
58
+ /**
59
+ * Create a new Cards layout
60
+ * @param items Optional initial items
61
+ */
62
+ constructor(items?: Record<string, any>[]) {
63
+ super();
64
+ if (items) {
65
+ this._items = items;
66
+ this._inferFields(items[0]);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Set section title
72
+ */
73
+ title(title: string): this {
74
+ this._options.title = title;
75
+ return this;
76
+ }
77
+
78
+ /**
79
+ * Set field for card image
80
+ */
81
+ image(field: string): this {
82
+ this._fields.image = field;
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Set field for card heading
88
+ */
89
+ heading(field: string): this {
90
+ this._fields.heading = field;
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Set field for card subtitle
96
+ */
97
+ subtitle(field: string): this {
98
+ this._fields.subtitle = field;
99
+ return this;
100
+ }
101
+
102
+ /**
103
+ * Set field for card description
104
+ */
105
+ description(field: string): this {
106
+ this._fields.description = field;
107
+ return this;
108
+ }
109
+
110
+ /**
111
+ * Set field for badge/tag
112
+ */
113
+ badge(field: string): this {
114
+ this._fields.badge = field;
115
+ return this;
116
+ }
117
+
118
+ /**
119
+ * Set field for footer text
120
+ */
121
+ footer(field: string): this {
122
+ this._fields.footer = field;
123
+ return this;
124
+ }
125
+
126
+ /**
127
+ * Set field for click link
128
+ */
129
+ link(field: string): this {
130
+ this._fields.link = field;
131
+ this._options.clickable = true;
132
+ return this;
133
+ }
134
+
135
+ /**
136
+ * Set fields for meta info
137
+ */
138
+ meta(...fields: string[]): this {
139
+ this._fields.meta = fields;
140
+ return this;
141
+ }
142
+
143
+ /**
144
+ * Set card items
145
+ */
146
+ items(data: Record<string, any>[]): this {
147
+ this._items = data;
148
+ if (Object.keys(this._fields).length === 0 && data.length > 0) {
149
+ this._inferFields(data[0]);
150
+ }
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Add a single item
156
+ */
157
+ item(data: Record<string, any>): this {
158
+ this._items.push(data);
159
+ return this;
160
+ }
161
+
162
+ /**
163
+ * Set number of columns
164
+ */
165
+ columns(count: 1 | 2 | 3 | 4 | 6): this {
166
+ this._options.columns = count;
167
+ return this;
168
+ }
169
+
170
+ /**
171
+ * Use compact card style
172
+ */
173
+ compact(enabled: boolean = true): this {
174
+ this._options.compact = enabled;
175
+ return this;
176
+ }
177
+
178
+ /**
179
+ * Enable click interaction
180
+ */
181
+ clickable(enabled: boolean = true): this {
182
+ this._options.clickable = enabled;
183
+ return this;
184
+ }
185
+
186
+ /**
187
+ * Enable hover effect
188
+ */
189
+ hoverable(enabled: boolean = true): this {
190
+ this._options.hoverable = enabled;
191
+ return this;
192
+ }
193
+
194
+ /**
195
+ * Show card borders
196
+ */
197
+ bordered(enabled: boolean = true): this {
198
+ this._options.bordered = enabled;
199
+ return this;
200
+ }
201
+
202
+ /**
203
+ * Set image aspect ratio
204
+ */
205
+ aspectRatio(ratio: 'square' | 'video' | 'portrait' | 'auto'): this {
206
+ this._options.aspectRatio = ratio;
207
+ return this;
208
+ }
209
+
210
+ /**
211
+ * Infer field mappings from data
212
+ */
213
+ private _inferFields(item: Record<string, any>): void {
214
+ const keys = Object.keys(item);
215
+
216
+ // Look for common field names
217
+ const findField = (patterns: string[]): string | undefined => {
218
+ return keys.find(k => patterns.some(p => k.toLowerCase().includes(p)));
219
+ };
220
+
221
+ this._fields.image = findField(['image', 'img', 'photo', 'picture', 'avatar', 'thumbnail']);
222
+ this._fields.heading = findField(['name', 'title', 'heading']);
223
+ this._fields.subtitle = findField(['subtitle', 'category', 'type']);
224
+ this._fields.description = findField(['description', 'summary', 'text', 'body', 'content']);
225
+ this._fields.badge = findField(['status', 'badge', 'tag', 'label']);
226
+ this._fields.link = findField(['url', 'link', 'href']);
227
+ }
228
+
229
+ /**
230
+ * Get item count
231
+ */
232
+ get length(): number {
233
+ return this._items.length;
234
+ }
235
+
236
+ toJSON() {
237
+ return {
238
+ _photonType: this._photonType,
239
+ items: this._items,
240
+ fields: this._fields,
241
+ options: this._options,
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Render as plain text for MCP clients
247
+ */
248
+ toString(): string {
249
+ const lines: string[] = [];
250
+
251
+ if (this._options.title) {
252
+ lines.push(`## ${this._options.title}`, '');
253
+ }
254
+
255
+ if (this._items.length === 0) {
256
+ lines.push('(No items)');
257
+ return lines.join('\n');
258
+ }
259
+
260
+ for (const item of this._items) {
261
+ // Heading
262
+ const heading = this._fields.heading ? item[this._fields.heading] : null;
263
+ if (heading) lines.push(`### ${heading}`);
264
+
265
+ // Subtitle
266
+ const subtitle = this._fields.subtitle ? item[this._fields.subtitle] : null;
267
+ if (subtitle) lines.push(`*${subtitle}*`);
268
+
269
+ // Badge
270
+ const badge = this._fields.badge ? item[this._fields.badge] : null;
271
+ if (badge) lines.push(`[${badge}]`);
272
+
273
+ // Description
274
+ const desc = this._fields.description ? item[this._fields.description] : null;
275
+ if (desc) lines.push('', desc);
276
+
277
+ // Link
278
+ const link = this._fields.link ? item[this._fields.link] : null;
279
+ if (link) lines.push('', `Link: ${link}`);
280
+
281
+ lines.push('');
282
+ }
283
+
284
+ return lines.join('\n').trim();
285
+ }
286
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Chart - Purpose-driven type for data visualization
3
+ *
4
+ * Automatically renders as a chart with the specified type.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * async revenue() {
9
+ * return new Chart('line')
10
+ * .title('Monthly Revenue')
11
+ * .labels(['Jan', 'Feb', 'Mar', 'Apr'])
12
+ * .series('Revenue', [1000, 1500, 1200, 1800])
13
+ * .series('Costs', [800, 900, 850, 950]);
14
+ * }
15
+ *
16
+ * async distribution() {
17
+ * return new Chart('pie')
18
+ * .title('User Distribution')
19
+ * .data([
20
+ * { label: 'Free', value: 1000 },
21
+ * { label: 'Pro', value: 500 },
22
+ * { label: 'Enterprise', value: 100 },
23
+ * ]);
24
+ * }
25
+ * ```
26
+ */
27
+
28
+ import { PhotonUIType } from './base.js';
29
+
30
+ export type ChartType = 'line' | 'bar' | 'pie' | 'doughnut' | 'area' | 'scatter' | 'radar';
31
+
32
+ export interface ChartSeries {
33
+ name: string;
34
+ data: number[];
35
+ color?: string;
36
+ }
37
+
38
+ export interface ChartDataPoint {
39
+ label: string;
40
+ value: number;
41
+ color?: string;
42
+ }
43
+
44
+ export interface ChartOptions {
45
+ title?: string;
46
+ subtitle?: string;
47
+ legend?: boolean | 'top' | 'bottom' | 'left' | 'right';
48
+ stacked?: boolean;
49
+ showGrid?: boolean;
50
+ showValues?: boolean;
51
+ animate?: boolean;
52
+ height?: number;
53
+ colors?: string[];
54
+ xAxisLabel?: string;
55
+ yAxisLabel?: string;
56
+ }
57
+
58
+ export class Chart extends PhotonUIType {
59
+ readonly _photonType = 'chart' as const;
60
+
61
+ private _type: ChartType;
62
+ private _labels: string[] = [];
63
+ private _series: ChartSeries[] = [];
64
+ private _data: ChartDataPoint[] = []; // For pie/doughnut
65
+ private _options: ChartOptions = {
66
+ legend: true,
67
+ showGrid: true,
68
+ animate: true,
69
+ };
70
+
71
+ /**
72
+ * Create a new Chart
73
+ * @param type Chart type (line, bar, pie, etc.)
74
+ */
75
+ constructor(type: ChartType = 'line') {
76
+ super();
77
+ this._type = type;
78
+ }
79
+
80
+ /**
81
+ * Set chart title
82
+ */
83
+ title(title: string): this {
84
+ this._options.title = title;
85
+ return this;
86
+ }
87
+
88
+ /**
89
+ * Set chart subtitle
90
+ */
91
+ subtitle(subtitle: string): this {
92
+ this._options.subtitle = subtitle;
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ * Set X-axis labels (for line, bar, area charts)
98
+ */
99
+ labels(labels: string[]): this {
100
+ this._labels = labels;
101
+ return this;
102
+ }
103
+
104
+ /**
105
+ * Add a data series (for line, bar, area charts)
106
+ */
107
+ series(name: string, data: number[], color?: string): this {
108
+ this._series.push({ name, data, color });
109
+ return this;
110
+ }
111
+
112
+ /**
113
+ * Set pie/doughnut data points
114
+ */
115
+ data(points: ChartDataPoint[] | Array<{ label: string; value: number }>): this {
116
+ this._data = points;
117
+ return this;
118
+ }
119
+
120
+ /**
121
+ * Configure legend
122
+ */
123
+ legend(position: boolean | 'top' | 'bottom' | 'left' | 'right' = true): this {
124
+ this._options.legend = position;
125
+ return this;
126
+ }
127
+
128
+ /**
129
+ * Enable stacked mode (for bar/area charts)
130
+ */
131
+ stacked(enabled: boolean = true): this {
132
+ this._options.stacked = enabled;
133
+ return this;
134
+ }
135
+
136
+ /**
137
+ * Show/hide grid lines
138
+ */
139
+ grid(enabled: boolean = true): this {
140
+ this._options.showGrid = enabled;
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Show values on chart
146
+ */
147
+ showValues(enabled: boolean = true): this {
148
+ this._options.showValues = enabled;
149
+ return this;
150
+ }
151
+
152
+ /**
153
+ * Enable/disable animations
154
+ */
155
+ animate(enabled: boolean = true): this {
156
+ this._options.animate = enabled;
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Set chart height
162
+ */
163
+ height(pixels: number): this {
164
+ this._options.height = pixels;
165
+ return this;
166
+ }
167
+
168
+ /**
169
+ * Set color palette
170
+ */
171
+ colors(colors: string[]): this {
172
+ this._options.colors = colors;
173
+ return this;
174
+ }
175
+
176
+ /**
177
+ * Set X-axis label
178
+ */
179
+ xAxis(label: string): this {
180
+ this._options.xAxisLabel = label;
181
+ return this;
182
+ }
183
+
184
+ /**
185
+ * Set Y-axis label
186
+ */
187
+ yAxis(label: string): this {
188
+ this._options.yAxisLabel = label;
189
+ return this;
190
+ }
191
+
192
+ toJSON() {
193
+ return {
194
+ _photonType: this._photonType,
195
+ chartType: this._type,
196
+ labels: this._labels,
197
+ series: this._series,
198
+ data: this._data,
199
+ options: this._options,
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Render as plain text for MCP clients
205
+ */
206
+ toString(): string {
207
+ const lines: string[] = [];
208
+
209
+ if (this._options.title) {
210
+ lines.push(`## ${this._options.title}`);
211
+ if (this._options.subtitle) lines.push(this._options.subtitle);
212
+ lines.push('');
213
+ }
214
+
215
+ // Pie/doughnut: show as list
216
+ if ((this._type === 'pie' || this._type === 'doughnut') && this._data.length > 0) {
217
+ const total = this._data.reduce((sum, d) => sum + d.value, 0);
218
+ for (const d of this._data) {
219
+ const pct = total > 0 ? ((d.value / total) * 100).toFixed(1) : '0';
220
+ lines.push(`- ${d.label}: ${d.value} (${pct}%)`);
221
+ }
222
+ return lines.join('\n');
223
+ }
224
+
225
+ // Series charts: show as table
226
+ if (this._series.length > 0 && this._labels.length > 0) {
227
+ const headers = ['', ...this._series.map(s => s.name)];
228
+ lines.push('| ' + headers.join(' | ') + ' |');
229
+ lines.push('| ' + headers.map(() => '---').join(' | ') + ' |');
230
+
231
+ for (let i = 0; i < this._labels.length; i++) {
232
+ const row = [this._labels[i], ...this._series.map(s => String(s.data[i] ?? ''))];
233
+ lines.push('| ' + row.join(' | ') + ' |');
234
+ }
235
+ }
236
+
237
+ return lines.join('\n');
238
+ }
239
+ }