@portel/photon-core 1.5.0 → 2.1.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 (163) hide show
  1. package/dist/auto-ui.js +1 -1
  2. package/dist/auto-ui.js.map +1 -1
  3. package/dist/base.d.ts +1 -1
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +2 -2
  6. package/dist/base.js.map +1 -1
  7. package/dist/cli-ui-renderer.js +1 -1
  8. package/dist/cli-ui-renderer.js.map +1 -1
  9. package/dist/design-system/index.d.ts +21 -0
  10. package/dist/design-system/index.d.ts.map +1 -0
  11. package/dist/design-system/index.js +27 -0
  12. package/dist/design-system/index.js.map +1 -0
  13. package/dist/design-system/tokens.d.ts +149 -0
  14. package/dist/design-system/tokens.d.ts.map +1 -0
  15. package/dist/design-system/tokens.js +413 -0
  16. package/dist/design-system/tokens.js.map +1 -0
  17. package/dist/design-system/transaction-ui.d.ts +70 -0
  18. package/dist/design-system/transaction-ui.d.ts.map +1 -0
  19. package/dist/design-system/transaction-ui.js +982 -0
  20. package/dist/design-system/transaction-ui.js.map +1 -0
  21. package/dist/generator.d.ts +56 -6
  22. package/dist/generator.d.ts.map +1 -1
  23. package/dist/generator.js.map +1 -1
  24. package/dist/index.d.ts +6 -7
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +46 -56
  27. package/dist/index.js.map +1 -1
  28. package/dist/io.d.ts +103 -2
  29. package/dist/io.d.ts.map +1 -1
  30. package/dist/io.js +37 -1
  31. package/dist/io.js.map +1 -1
  32. package/dist/rendering/components.d.ts +29 -0
  33. package/dist/rendering/components.d.ts.map +1 -0
  34. package/dist/rendering/components.js +773 -0
  35. package/dist/rendering/components.js.map +1 -0
  36. package/dist/rendering/field-analyzer.d.ts +48 -0
  37. package/dist/rendering/field-analyzer.d.ts.map +1 -0
  38. package/dist/rendering/field-analyzer.js +270 -0
  39. package/dist/rendering/field-analyzer.js.map +1 -0
  40. package/dist/rendering/field-renderers.d.ts +64 -0
  41. package/dist/rendering/field-renderers.d.ts.map +1 -0
  42. package/dist/rendering/field-renderers.js +317 -0
  43. package/dist/rendering/field-renderers.js.map +1 -0
  44. package/dist/rendering/index.d.ts +28 -0
  45. package/dist/rendering/index.d.ts.map +1 -0
  46. package/dist/rendering/index.js +60 -0
  47. package/dist/rendering/index.js.map +1 -0
  48. package/dist/rendering/layout-selector.d.ts +48 -0
  49. package/dist/rendering/layout-selector.d.ts.map +1 -0
  50. package/dist/rendering/layout-selector.js +347 -0
  51. package/dist/rendering/layout-selector.js.map +1 -0
  52. package/dist/rendering/template-engine.d.ts +41 -0
  53. package/dist/rendering/template-engine.d.ts.map +1 -0
  54. package/dist/rendering/template-engine.js +236 -0
  55. package/dist/rendering/template-engine.js.map +1 -0
  56. package/dist/schema-extractor.d.ts +30 -0
  57. package/dist/schema-extractor.d.ts.map +1 -1
  58. package/dist/schema-extractor.js +205 -12
  59. package/dist/schema-extractor.js.map +1 -1
  60. package/dist/stateful.js +1 -1
  61. package/dist/stateful.js.map +1 -1
  62. package/dist/types.d.ts +9 -1
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js.map +1 -1
  65. package/dist/ucp/ap2/handlers.d.ts +242 -0
  66. package/dist/ucp/ap2/handlers.d.ts.map +1 -0
  67. package/dist/ucp/ap2/handlers.js +482 -0
  68. package/dist/ucp/ap2/handlers.js.map +1 -0
  69. package/dist/ucp/ap2/mandates.d.ts +95 -0
  70. package/dist/ucp/ap2/mandates.d.ts.map +1 -0
  71. package/dist/ucp/ap2/mandates.js +234 -0
  72. package/dist/ucp/ap2/mandates.js.map +1 -0
  73. package/dist/ucp/ap2/types.d.ts +305 -0
  74. package/dist/ucp/ap2/types.d.ts.map +1 -0
  75. package/dist/ucp/ap2/types.js +8 -0
  76. package/dist/ucp/ap2/types.js.map +1 -0
  77. package/dist/ucp/capabilities/checkout.d.ts +118 -0
  78. package/dist/ucp/capabilities/checkout.d.ts.map +1 -0
  79. package/dist/ucp/capabilities/checkout.js +344 -0
  80. package/dist/ucp/capabilities/checkout.js.map +1 -0
  81. package/dist/ucp/capabilities/identity.d.ts +130 -0
  82. package/dist/ucp/capabilities/identity.d.ts.map +1 -0
  83. package/dist/ucp/capabilities/identity.js +290 -0
  84. package/dist/ucp/capabilities/identity.js.map +1 -0
  85. package/dist/ucp/capabilities/order.d.ts +142 -0
  86. package/dist/ucp/capabilities/order.d.ts.map +1 -0
  87. package/dist/ucp/capabilities/order.js +383 -0
  88. package/dist/ucp/capabilities/order.js.map +1 -0
  89. package/dist/ucp/index.d.ts +18 -0
  90. package/dist/ucp/index.d.ts.map +1 -0
  91. package/dist/ucp/index.js +19 -0
  92. package/dist/ucp/index.js.map +1 -0
  93. package/dist/ucp/manifest.d.ts +62 -0
  94. package/dist/ucp/manifest.d.ts.map +1 -0
  95. package/dist/ucp/manifest.js +180 -0
  96. package/dist/ucp/manifest.js.map +1 -0
  97. package/dist/ucp/types.d.ts +327 -0
  98. package/dist/ucp/types.d.ts.map +1 -0
  99. package/dist/ucp/types.js +8 -0
  100. package/dist/ucp/types.js.map +1 -0
  101. package/package.json +3 -4
  102. package/src/auto-ui.ts +1 -1
  103. package/src/base.ts +2 -2
  104. package/src/cli-ui-renderer.ts +1 -1
  105. package/src/design-system/index.ts +30 -0
  106. package/src/design-system/tokens.ts +451 -0
  107. package/src/design-system/transaction-ui.ts +1038 -0
  108. package/src/generator.ts +58 -2
  109. package/src/index.ts +135 -124
  110. package/src/io.ts +108 -3
  111. package/src/rendering/components.ts +785 -0
  112. package/src/rendering/field-analyzer.ts +299 -0
  113. package/src/rendering/field-renderers.ts +356 -0
  114. package/src/rendering/index.ts +63 -0
  115. package/src/rendering/layout-selector.ts +390 -0
  116. package/src/rendering/template-engine.ts +254 -0
  117. package/src/schema-extractor.ts +225 -12
  118. package/src/stateful.ts +1 -1
  119. package/src/types.ts +10 -1
  120. package/src/ucp/ap2/handlers.ts +779 -0
  121. package/src/ucp/ap2/mandates.ts +354 -0
  122. package/src/ucp/ap2/types.ts +441 -0
  123. package/src/ucp/capabilities/checkout.ts +497 -0
  124. package/src/ucp/capabilities/identity.ts +425 -0
  125. package/src/ucp/capabilities/order.ts +549 -0
  126. package/src/ucp/index.ts +27 -0
  127. package/src/ucp/manifest.ts +257 -0
  128. package/src/ucp/types.ts +454 -0
  129. package/dist/cli-formatter.d.ts +0 -92
  130. package/dist/cli-formatter.d.ts.map +0 -1
  131. package/dist/cli-formatter.js +0 -486
  132. package/dist/cli-formatter.js.map +0 -1
  133. package/dist/context.d.ts +0 -6
  134. package/dist/context.d.ts.map +0 -1
  135. package/dist/context.js +0 -3
  136. package/dist/context.js.map +0 -1
  137. package/dist/elicit.d.ts +0 -93
  138. package/dist/elicit.d.ts.map +0 -1
  139. package/dist/elicit.js +0 -373
  140. package/dist/elicit.js.map +0 -1
  141. package/dist/mcp-client.d.ts +0 -218
  142. package/dist/mcp-client.d.ts.map +0 -1
  143. package/dist/mcp-client.js +0 -424
  144. package/dist/mcp-client.js.map +0 -1
  145. package/dist/mcp-sdk-transport.d.ts +0 -88
  146. package/dist/mcp-sdk-transport.d.ts.map +0 -1
  147. package/dist/mcp-sdk-transport.js +0 -360
  148. package/dist/mcp-sdk-transport.js.map +0 -1
  149. package/dist/photon-config.d.ts +0 -86
  150. package/dist/photon-config.d.ts.map +0 -1
  151. package/dist/photon-config.js +0 -156
  152. package/dist/photon-config.js.map +0 -1
  153. package/dist/progress.d.ts +0 -93
  154. package/dist/progress.d.ts.map +0 -1
  155. package/dist/progress.js +0 -195
  156. package/dist/progress.js.map +0 -1
  157. package/src/cli-formatter.ts +0 -579
  158. package/src/context.ts +0 -7
  159. package/src/elicit.ts +0 -438
  160. package/src/mcp-client.ts +0 -561
  161. package/src/mcp-sdk-transport.ts +0 -449
  162. package/src/photon-config.ts +0 -201
  163. package/src/progress.ts +0 -224
@@ -0,0 +1,1038 @@
1
+ /**
2
+ * Photon Transaction UI System
3
+ *
4
+ * A focused UI system for completing decisions in AI+Human workflows.
5
+ * Not a general UI framework - purpose-built for the 4 transaction states:
6
+ *
7
+ * 1. SELECT - Pick from options (AI narrowed down, human selects visually)
8
+ * 2. CONFIRM - Review before action (summary of what will happen)
9
+ * 3. INPUT - Fill required details (smart form with validation)
10
+ * 4. RESULT - Show outcome (success/error status + receipt)
11
+ */
12
+
13
+ // =============================================================================
14
+ // TRANSACTION STATE TYPES
15
+ // =============================================================================
16
+
17
+ export type TransactionState = 'select' | 'confirm' | 'input' | 'result';
18
+
19
+ export interface SelectionItem {
20
+ id: string;
21
+ title: string;
22
+ subtitle?: string;
23
+ image?: string;
24
+ icon?: string;
25
+ badge?: string;
26
+ detail?: string;
27
+ metadata?: Record<string, any>;
28
+ }
29
+
30
+ export interface ConfirmationData {
31
+ title: string;
32
+ description?: string;
33
+ items: Array<{ label: string; value: string }>;
34
+ warning?: string;
35
+ actions: {
36
+ confirm: string;
37
+ cancel?: string;
38
+ };
39
+ }
40
+
41
+ export interface InputField {
42
+ name: string;
43
+ label: string;
44
+ type: 'text' | 'number' | 'email' | 'password' | 'date' | 'select' | 'textarea';
45
+ placeholder?: string;
46
+ hint?: string;
47
+ required?: boolean;
48
+ options?: Array<{ label: string; value: string }>;
49
+ validation?: {
50
+ pattern?: string;
51
+ min?: number;
52
+ max?: number;
53
+ minLength?: number;
54
+ maxLength?: number;
55
+ };
56
+ }
57
+
58
+ export interface ResultData {
59
+ status: 'success' | 'error' | 'pending';
60
+ title: string;
61
+ message?: string;
62
+ details?: Array<{ label: string; value: string }>;
63
+ actions?: Array<{ label: string; action: string }>;
64
+ }
65
+
66
+ // =============================================================================
67
+ // CSS GENERATION
68
+ // =============================================================================
69
+
70
+ export function generateTransactionCSS(): string {
71
+ return `
72
+ /* ==========================================================================
73
+ Photon Transaction UI
74
+ Purpose-built for AI+Human decision workflows
75
+ ========================================================================== */
76
+
77
+ /* -----------------------------------------------------------------------------
78
+ Base Reset & Foundation
79
+ ----------------------------------------------------------------------------- */
80
+
81
+ .photon-tx {
82
+ font-family: var(--font-sans);
83
+ font-size: var(--text-body-md);
84
+ line-height: var(--leading-body-md);
85
+ color: var(--color-on-surface);
86
+ -webkit-font-smoothing: antialiased;
87
+ -moz-osx-font-smoothing: grayscale;
88
+ }
89
+
90
+ .photon-tx * {
91
+ box-sizing: border-box;
92
+ }
93
+
94
+ /* -----------------------------------------------------------------------------
95
+ SELECT State - Pick from options
96
+ ----------------------------------------------------------------------------- */
97
+
98
+ /* Selection Grid - for visual items (images) */
99
+ .tx-select-grid {
100
+ display: grid;
101
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
102
+ gap: var(--space-4);
103
+ }
104
+
105
+ .tx-select-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
106
+ .tx-select-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
107
+ .tx-select-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
108
+
109
+ /* Selection Card */
110
+ .tx-select-card {
111
+ display: flex;
112
+ flex-direction: column;
113
+ background: var(--color-surface-container);
114
+ border: 2px solid transparent;
115
+ border-radius: var(--radius-md);
116
+ overflow: hidden;
117
+ cursor: pointer;
118
+ transition: all var(--duration-normal) var(--ease-standard);
119
+ }
120
+
121
+ .tx-select-card:hover {
122
+ background: var(--color-surface-container-high);
123
+ box-shadow: var(--elevation-2);
124
+ transform: translateY(-2px);
125
+ }
126
+
127
+ .tx-select-card:active {
128
+ transform: translateY(0);
129
+ }
130
+
131
+ .tx-select-card.selected {
132
+ border-color: var(--color-primary);
133
+ background: var(--color-surface-container-high);
134
+ }
135
+
136
+ .tx-select-card-image {
137
+ width: 100%;
138
+ aspect-ratio: 4/3;
139
+ object-fit: cover;
140
+ background: var(--color-surface-container-highest);
141
+ }
142
+
143
+ .tx-select-card-image.placeholder {
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ font-size: var(--text-display-sm);
148
+ color: var(--color-on-surface-muted);
149
+ }
150
+
151
+ .tx-select-card-body {
152
+ padding: var(--space-3);
153
+ display: flex;
154
+ flex-direction: column;
155
+ gap: var(--space-1);
156
+ }
157
+
158
+ .tx-select-card-title {
159
+ font-size: var(--text-title-md);
160
+ font-weight: var(--weight-medium);
161
+ color: var(--color-on-surface);
162
+ white-space: nowrap;
163
+ overflow: hidden;
164
+ text-overflow: ellipsis;
165
+ }
166
+
167
+ .tx-select-card-subtitle {
168
+ font-size: var(--text-body-sm);
169
+ color: var(--color-on-surface-variant);
170
+ white-space: nowrap;
171
+ overflow: hidden;
172
+ text-overflow: ellipsis;
173
+ }
174
+
175
+ .tx-select-card-footer {
176
+ display: flex;
177
+ justify-content: space-between;
178
+ align-items: center;
179
+ margin-top: var(--space-2);
180
+ }
181
+
182
+ .tx-select-card-detail {
183
+ font-size: var(--text-title-md);
184
+ font-weight: var(--weight-semibold);
185
+ color: var(--color-on-surface);
186
+ }
187
+
188
+ .tx-select-card-badge {
189
+ padding: var(--space-1) var(--space-2);
190
+ border-radius: var(--radius-full);
191
+ font-size: var(--text-label-sm);
192
+ font-weight: var(--weight-medium);
193
+ background: var(--color-primary-container);
194
+ color: var(--color-on-primary-container);
195
+ }
196
+
197
+ /* Selection List - for text-focused items */
198
+ .tx-select-list {
199
+ display: flex;
200
+ flex-direction: column;
201
+ background: var(--color-surface-container);
202
+ border-radius: var(--radius-md);
203
+ overflow: hidden;
204
+ }
205
+
206
+ .tx-select-item {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: var(--space-3);
210
+ padding: var(--space-3) var(--space-4);
211
+ min-height: var(--touch-min);
212
+ border-bottom: 1px solid var(--color-outline-variant);
213
+ cursor: pointer;
214
+ transition: background var(--duration-fast) var(--ease-standard);
215
+ }
216
+
217
+ .tx-select-item:last-child {
218
+ border-bottom: none;
219
+ }
220
+
221
+ .tx-select-item:hover {
222
+ background: var(--color-surface-container-high);
223
+ }
224
+
225
+ .tx-select-item:active {
226
+ background: var(--color-surface-container-highest);
227
+ }
228
+
229
+ .tx-select-item.selected {
230
+ background: var(--color-primary-container);
231
+ }
232
+
233
+ .tx-select-item-leading {
234
+ flex-shrink: 0;
235
+ width: 40px;
236
+ height: 40px;
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: center;
240
+ border-radius: var(--radius-sm);
241
+ background: var(--color-surface-container-highest);
242
+ font-size: var(--text-title-lg);
243
+ }
244
+
245
+ .tx-select-item-leading img {
246
+ width: 100%;
247
+ height: 100%;
248
+ object-fit: cover;
249
+ border-radius: var(--radius-sm);
250
+ }
251
+
252
+ .tx-select-item-content {
253
+ flex: 1;
254
+ min-width: 0;
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 2px;
258
+ }
259
+
260
+ .tx-select-item-title {
261
+ font-size: var(--text-body-lg);
262
+ font-weight: var(--weight-medium);
263
+ color: var(--color-on-surface);
264
+ white-space: nowrap;
265
+ overflow: hidden;
266
+ text-overflow: ellipsis;
267
+ }
268
+
269
+ .tx-select-item-subtitle {
270
+ font-size: var(--text-body-sm);
271
+ color: var(--color-on-surface-variant);
272
+ white-space: nowrap;
273
+ overflow: hidden;
274
+ text-overflow: ellipsis;
275
+ }
276
+
277
+ .tx-select-item-trailing {
278
+ flex-shrink: 0;
279
+ display: flex;
280
+ align-items: center;
281
+ gap: var(--space-2);
282
+ }
283
+
284
+ .tx-select-item-detail {
285
+ font-size: var(--text-body-md);
286
+ font-weight: var(--weight-medium);
287
+ color: var(--color-on-surface);
288
+ }
289
+
290
+ .tx-select-item-chevron {
291
+ color: var(--color-on-surface-muted);
292
+ font-size: var(--text-title-md);
293
+ }
294
+
295
+ /* Status Badges */
296
+ .tx-badge {
297
+ padding: var(--space-1) var(--space-2);
298
+ border-radius: var(--radius-full);
299
+ font-size: var(--text-label-sm);
300
+ font-weight: var(--weight-medium);
301
+ }
302
+
303
+ .tx-badge-success {
304
+ background: var(--color-success-container);
305
+ color: var(--color-on-success-container);
306
+ }
307
+
308
+ .tx-badge-warning {
309
+ background: var(--color-warning-container);
310
+ color: var(--color-on-warning-container);
311
+ }
312
+
313
+ .tx-badge-error {
314
+ background: var(--color-error-container);
315
+ color: var(--color-on-error-container);
316
+ }
317
+
318
+ .tx-badge-neutral {
319
+ background: var(--color-surface-container-highest);
320
+ color: var(--color-on-surface-variant);
321
+ }
322
+
323
+ /* -----------------------------------------------------------------------------
324
+ CONFIRM State - Review before action
325
+ ----------------------------------------------------------------------------- */
326
+
327
+ .tx-confirm {
328
+ background: var(--color-surface-container);
329
+ border-radius: var(--radius-lg);
330
+ overflow: hidden;
331
+ }
332
+
333
+ .tx-confirm-header {
334
+ padding: var(--space-4);
335
+ border-bottom: 1px solid var(--color-outline-variant);
336
+ }
337
+
338
+ .tx-confirm-title {
339
+ font-size: var(--text-headline-sm);
340
+ font-weight: var(--weight-semibold);
341
+ color: var(--color-on-surface);
342
+ margin: 0;
343
+ }
344
+
345
+ .tx-confirm-description {
346
+ font-size: var(--text-body-md);
347
+ color: var(--color-on-surface-variant);
348
+ margin-top: var(--space-2);
349
+ }
350
+
351
+ .tx-confirm-body {
352
+ padding: 0;
353
+ }
354
+
355
+ .tx-confirm-row {
356
+ display: flex;
357
+ justify-content: space-between;
358
+ align-items: center;
359
+ padding: var(--space-3) var(--space-4);
360
+ border-bottom: 1px solid var(--color-outline-variant);
361
+ }
362
+
363
+ .tx-confirm-row:last-child {
364
+ border-bottom: none;
365
+ }
366
+
367
+ .tx-confirm-label {
368
+ font-size: var(--text-body-md);
369
+ color: var(--color-on-surface-variant);
370
+ }
371
+
372
+ .tx-confirm-value {
373
+ font-size: var(--text-body-md);
374
+ font-weight: var(--weight-medium);
375
+ color: var(--color-on-surface);
376
+ text-align: right;
377
+ }
378
+
379
+ .tx-confirm-warning {
380
+ display: flex;
381
+ align-items: flex-start;
382
+ gap: var(--space-2);
383
+ padding: var(--space-3) var(--space-4);
384
+ background: var(--color-warning-container);
385
+ color: var(--color-on-warning-container);
386
+ font-size: var(--text-body-sm);
387
+ }
388
+
389
+ .tx-confirm-warning-icon {
390
+ flex-shrink: 0;
391
+ font-size: var(--text-title-md);
392
+ }
393
+
394
+ .tx-confirm-actions {
395
+ display: flex;
396
+ gap: var(--space-3);
397
+ padding: var(--space-4);
398
+ border-top: 1px solid var(--color-outline-variant);
399
+ }
400
+
401
+ /* -----------------------------------------------------------------------------
402
+ INPUT State - Fill required details
403
+ ----------------------------------------------------------------------------- */
404
+
405
+ .tx-input-form {
406
+ display: flex;
407
+ flex-direction: column;
408
+ gap: var(--space-4);
409
+ }
410
+
411
+ .tx-input-field {
412
+ display: flex;
413
+ flex-direction: column;
414
+ gap: var(--space-1);
415
+ }
416
+
417
+ .tx-input-label {
418
+ font-size: var(--text-label-lg);
419
+ font-weight: var(--weight-medium);
420
+ color: var(--color-on-surface);
421
+ }
422
+
423
+ .tx-input-label .required {
424
+ color: var(--color-error);
425
+ margin-left: var(--space-1);
426
+ }
427
+
428
+ .tx-input-control {
429
+ width: 100%;
430
+ padding: var(--space-3);
431
+ min-height: var(--touch-min);
432
+ font-family: inherit;
433
+ font-size: var(--text-body-lg);
434
+ color: var(--color-on-surface);
435
+ background: var(--color-surface-container);
436
+ border: 1px solid var(--color-outline);
437
+ border-radius: var(--radius-md);
438
+ outline: none;
439
+ transition: border-color var(--duration-fast) var(--ease-standard),
440
+ box-shadow var(--duration-fast) var(--ease-standard);
441
+ }
442
+
443
+ .tx-input-control::placeholder {
444
+ color: var(--color-on-surface-muted);
445
+ }
446
+
447
+ .tx-input-control:hover {
448
+ border-color: var(--color-on-surface-variant);
449
+ }
450
+
451
+ .tx-input-control:focus {
452
+ border-color: var(--color-primary);
453
+ box-shadow: 0 0 0 3px var(--color-primary-container);
454
+ }
455
+
456
+ .tx-input-control.error {
457
+ border-color: var(--color-error);
458
+ }
459
+
460
+ .tx-input-control.error:focus {
461
+ box-shadow: 0 0 0 3px var(--color-error-container);
462
+ }
463
+
464
+ .tx-input-hint {
465
+ font-size: var(--text-body-sm);
466
+ color: var(--color-on-surface-muted);
467
+ }
468
+
469
+ .tx-input-error {
470
+ font-size: var(--text-body-sm);
471
+ color: var(--color-error);
472
+ }
473
+
474
+ textarea.tx-input-control {
475
+ min-height: 100px;
476
+ resize: vertical;
477
+ }
478
+
479
+ select.tx-input-control {
480
+ appearance: none;
481
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23808080' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
482
+ background-repeat: no-repeat;
483
+ background-position: right var(--space-3) center;
484
+ padding-right: var(--space-8);
485
+ }
486
+
487
+ /* -----------------------------------------------------------------------------
488
+ RESULT State - Show outcome
489
+ ----------------------------------------------------------------------------- */
490
+
491
+ .tx-result {
492
+ display: flex;
493
+ flex-direction: column;
494
+ align-items: center;
495
+ text-align: center;
496
+ padding: var(--space-8) var(--space-4);
497
+ background: var(--color-surface-container);
498
+ border-radius: var(--radius-lg);
499
+ }
500
+
501
+ .tx-result-icon {
502
+ width: 64px;
503
+ height: 64px;
504
+ display: flex;
505
+ align-items: center;
506
+ justify-content: center;
507
+ border-radius: var(--radius-full);
508
+ font-size: var(--text-display-sm);
509
+ margin-bottom: var(--space-4);
510
+ }
511
+
512
+ .tx-result-icon.success {
513
+ background: var(--color-success-container);
514
+ color: var(--color-success);
515
+ }
516
+
517
+ .tx-result-icon.error {
518
+ background: var(--color-error-container);
519
+ color: var(--color-error);
520
+ }
521
+
522
+ .tx-result-icon.pending {
523
+ background: var(--color-warning-container);
524
+ color: var(--color-warning);
525
+ }
526
+
527
+ .tx-result-title {
528
+ font-size: var(--text-headline-md);
529
+ font-weight: var(--weight-semibold);
530
+ color: var(--color-on-surface);
531
+ margin: 0;
532
+ }
533
+
534
+ .tx-result-message {
535
+ font-size: var(--text-body-lg);
536
+ color: var(--color-on-surface-variant);
537
+ margin-top: var(--space-2);
538
+ max-width: 400px;
539
+ }
540
+
541
+ .tx-result-details {
542
+ width: 100%;
543
+ max-width: 400px;
544
+ margin-top: var(--space-6);
545
+ background: var(--color-surface-container-high);
546
+ border-radius: var(--radius-md);
547
+ overflow: hidden;
548
+ }
549
+
550
+ .tx-result-detail-row {
551
+ display: flex;
552
+ justify-content: space-between;
553
+ padding: var(--space-3) var(--space-4);
554
+ border-bottom: 1px solid var(--color-outline-variant);
555
+ }
556
+
557
+ .tx-result-detail-row:last-child {
558
+ border-bottom: none;
559
+ }
560
+
561
+ .tx-result-detail-label {
562
+ font-size: var(--text-body-sm);
563
+ color: var(--color-on-surface-muted);
564
+ }
565
+
566
+ .tx-result-detail-value {
567
+ font-size: var(--text-body-sm);
568
+ font-weight: var(--weight-medium);
569
+ color: var(--color-on-surface);
570
+ }
571
+
572
+ .tx-result-actions {
573
+ display: flex;
574
+ gap: var(--space-3);
575
+ margin-top: var(--space-6);
576
+ }
577
+
578
+ /* -----------------------------------------------------------------------------
579
+ Buttons
580
+ ----------------------------------------------------------------------------- */
581
+
582
+ .tx-btn {
583
+ display: inline-flex;
584
+ align-items: center;
585
+ justify-content: center;
586
+ gap: var(--space-2);
587
+ min-height: var(--touch-min);
588
+ padding: 0 var(--space-5);
589
+ font-family: inherit;
590
+ font-size: var(--text-label-lg);
591
+ font-weight: var(--weight-medium);
592
+ border-radius: var(--radius-full);
593
+ border: none;
594
+ cursor: pointer;
595
+ transition: all var(--duration-fast) var(--ease-standard);
596
+ }
597
+
598
+ .tx-btn:focus-visible {
599
+ outline: 2px solid var(--color-primary);
600
+ outline-offset: 2px;
601
+ }
602
+
603
+ .tx-btn-primary {
604
+ background: var(--color-primary);
605
+ color: var(--color-on-primary);
606
+ }
607
+
608
+ .tx-btn-primary:hover {
609
+ box-shadow: var(--elevation-1);
610
+ filter: brightness(1.1);
611
+ }
612
+
613
+ .tx-btn-primary:active {
614
+ filter: brightness(0.95);
615
+ }
616
+
617
+ .tx-btn-secondary {
618
+ background: var(--color-surface-container-high);
619
+ color: var(--color-on-surface);
620
+ border: 1px solid var(--color-outline);
621
+ }
622
+
623
+ .tx-btn-secondary:hover {
624
+ background: var(--color-surface-container-highest);
625
+ }
626
+
627
+ .tx-btn-ghost {
628
+ background: transparent;
629
+ color: var(--color-primary);
630
+ }
631
+
632
+ .tx-btn-ghost:hover {
633
+ background: var(--color-primary-container);
634
+ }
635
+
636
+ .tx-btn-danger {
637
+ background: var(--color-error);
638
+ color: var(--color-on-error);
639
+ }
640
+
641
+ .tx-btn-danger:hover {
642
+ filter: brightness(1.1);
643
+ }
644
+
645
+ .tx-btn:disabled {
646
+ opacity: 0.5;
647
+ cursor: not-allowed;
648
+ }
649
+
650
+ .tx-btn.full-width {
651
+ width: 100%;
652
+ }
653
+
654
+ /* -----------------------------------------------------------------------------
655
+ Empty State
656
+ ----------------------------------------------------------------------------- */
657
+
658
+ .tx-empty {
659
+ display: flex;
660
+ flex-direction: column;
661
+ align-items: center;
662
+ justify-content: center;
663
+ padding: var(--space-8);
664
+ text-align: center;
665
+ color: var(--color-on-surface-muted);
666
+ }
667
+
668
+ .tx-empty-icon {
669
+ font-size: var(--text-display-md);
670
+ margin-bottom: var(--space-4);
671
+ opacity: 0.5;
672
+ }
673
+
674
+ .tx-empty-title {
675
+ font-size: var(--text-title-md);
676
+ font-weight: var(--weight-medium);
677
+ color: var(--color-on-surface-variant);
678
+ }
679
+
680
+ .tx-empty-message {
681
+ font-size: var(--text-body-md);
682
+ margin-top: var(--space-2);
683
+ }
684
+
685
+ /* -----------------------------------------------------------------------------
686
+ Loading State
687
+ ----------------------------------------------------------------------------- */
688
+
689
+ .tx-loading {
690
+ display: flex;
691
+ flex-direction: column;
692
+ align-items: center;
693
+ justify-content: center;
694
+ padding: var(--space-8);
695
+ }
696
+
697
+ .tx-spinner {
698
+ width: 40px;
699
+ height: 40px;
700
+ border: 3px solid var(--color-outline-variant);
701
+ border-top-color: var(--color-primary);
702
+ border-radius: var(--radius-full);
703
+ animation: tx-spin 1s linear infinite;
704
+ }
705
+
706
+ @keyframes tx-spin {
707
+ to { transform: rotate(360deg); }
708
+ }
709
+
710
+ .tx-loading-text {
711
+ font-size: var(--text-body-md);
712
+ color: var(--color-on-surface-variant);
713
+ margin-top: var(--space-4);
714
+ }
715
+ `;
716
+ }
717
+
718
+ // =============================================================================
719
+ // JAVASCRIPT GENERATION (for embedded HTML)
720
+ // =============================================================================
721
+
722
+ export function generateTransactionJS(): string {
723
+ return `
724
+ // Photon Transaction UI JavaScript
725
+
726
+ /**
727
+ * Detect transaction state from data
728
+ */
729
+ function detectTransactionState(data, method) {
730
+ // Check for result patterns
731
+ if (data && typeof data === 'object') {
732
+ if ('status' in data && ('success' in data || 'error' in data || 'message' in data)) {
733
+ return 'result';
734
+ }
735
+ if ('confirm' in data || 'warning' in data || 'actions' in data) {
736
+ return 'confirm';
737
+ }
738
+ }
739
+
740
+ // Arrays with selectable items -> select state
741
+ if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object') {
742
+ return 'select';
743
+ }
744
+
745
+ // Default to showing as data
746
+ return null;
747
+ }
748
+
749
+ /**
750
+ * Render Selection Grid (for visual items)
751
+ */
752
+ function renderSelectGrid(items, mapping, columns) {
753
+ if (!items || items.length === 0) {
754
+ return renderEmpty('No items to select');
755
+ }
756
+
757
+ const colClass = columns ? 'cols-' + columns : '';
758
+
759
+ return '<div class="tx-select-grid ' + colClass + '">' +
760
+ items.map((item, i) => renderSelectCard(item, mapping, i)).join('') +
761
+ '</div>';
762
+ }
763
+
764
+ /**
765
+ * Render Selection Card
766
+ */
767
+ function renderSelectCard(item, mapping, index) {
768
+ const id = item[mapping.id] || item.id || index;
769
+ const title = item[mapping.title] || item.title || item.name || 'Item ' + (index + 1);
770
+ const subtitle = item[mapping.subtitle] || item.subtitle || item.description || '';
771
+ const image = item[mapping.image] || item.image || item.photo || item.thumbnail;
772
+ const icon = item[mapping.icon] || item.icon || item.avatar;
773
+ const detail = item[mapping.detail] || item.detail || item.price;
774
+ const badge = item[mapping.badge] || item.badge || item.status;
775
+
776
+ let imageHtml = '';
777
+ if (image) {
778
+ imageHtml = '<img class="tx-select-card-image" src="' + escapeHtml(image) + '" alt="" loading="lazy">';
779
+ } else if (icon) {
780
+ imageHtml = '<div class="tx-select-card-image placeholder">' + escapeHtml(icon) + '</div>';
781
+ } else {
782
+ imageHtml = '<div class="tx-select-card-image placeholder">📦</div>';
783
+ }
784
+
785
+ let footerHtml = '';
786
+ if (detail || badge) {
787
+ footerHtml = '<div class="tx-select-card-footer">';
788
+ if (detail) {
789
+ footerHtml += '<span class="tx-select-card-detail">' + escapeHtml(formatValue(detail)) + '</span>';
790
+ }
791
+ if (badge) {
792
+ footerHtml += '<span class="tx-select-card-badge ' + getBadgeClass(badge) + '">' + escapeHtml(badge) + '</span>';
793
+ }
794
+ footerHtml += '</div>';
795
+ }
796
+
797
+ return '<div class="tx-select-card" data-id="' + escapeHtml(String(id)) + '" onclick="handleSelect(this)">' +
798
+ imageHtml +
799
+ '<div class="tx-select-card-body">' +
800
+ '<div class="tx-select-card-title">' + escapeHtml(title) + '</div>' +
801
+ (subtitle ? '<div class="tx-select-card-subtitle">' + escapeHtml(subtitle) + '</div>' : '') +
802
+ footerHtml +
803
+ '</div>' +
804
+ '</div>';
805
+ }
806
+
807
+ /**
808
+ * Render Selection List (for text-focused items)
809
+ */
810
+ function renderSelectList(items, mapping) {
811
+ if (!items || items.length === 0) {
812
+ return renderEmpty('No items to select');
813
+ }
814
+
815
+ return '<div class="tx-select-list">' +
816
+ items.map((item, i) => renderSelectItem(item, mapping, i)).join('') +
817
+ '</div>';
818
+ }
819
+
820
+ /**
821
+ * Render Selection List Item
822
+ */
823
+ function renderSelectItem(item, mapping, index) {
824
+ const id = item[mapping.id] || item.id || index;
825
+ const title = item[mapping.title] || item.title || item.name || 'Item ' + (index + 1);
826
+ const subtitle = item[mapping.subtitle] || item.subtitle || item.description || item.email || '';
827
+ const icon = item[mapping.icon] || item.icon || item.avatar;
828
+ const detail = item[mapping.detail] || item.detail;
829
+ const badge = item[mapping.badge] || item.badge || item.status;
830
+
831
+ let leadingHtml = '';
832
+ if (icon) {
833
+ if (icon.startsWith('http') || icon.startsWith('/')) {
834
+ leadingHtml = '<div class="tx-select-item-leading"><img src="' + escapeHtml(icon) + '" alt=""></div>';
835
+ } else {
836
+ leadingHtml = '<div class="tx-select-item-leading">' + escapeHtml(icon) + '</div>';
837
+ }
838
+ }
839
+
840
+ let trailingHtml = '<div class="tx-select-item-trailing">';
841
+ if (detail) {
842
+ trailingHtml += '<span class="tx-select-item-detail">' + escapeHtml(formatValue(detail)) + '</span>';
843
+ }
844
+ if (badge) {
845
+ trailingHtml += '<span class="tx-badge ' + getBadgeClass(badge) + '">' + escapeHtml(badge) + '</span>';
846
+ }
847
+ trailingHtml += '<span class="tx-select-item-chevron">›</span></div>';
848
+
849
+ return '<div class="tx-select-item" data-id="' + escapeHtml(String(id)) + '" onclick="handleSelect(this)">' +
850
+ leadingHtml +
851
+ '<div class="tx-select-item-content">' +
852
+ '<div class="tx-select-item-title">' + escapeHtml(title) + '</div>' +
853
+ (subtitle ? '<div class="tx-select-item-subtitle">' + escapeHtml(subtitle) + '</div>' : '') +
854
+ '</div>' +
855
+ trailingHtml +
856
+ '</div>';
857
+ }
858
+
859
+ /**
860
+ * Render Confirmation Card
861
+ */
862
+ function renderConfirm(data) {
863
+ let html = '<div class="tx-confirm">';
864
+
865
+ // Header
866
+ html += '<div class="tx-confirm-header">';
867
+ html += '<h3 class="tx-confirm-title">' + escapeHtml(data.title || 'Confirm') + '</h3>';
868
+ if (data.description) {
869
+ html += '<p class="tx-confirm-description">' + escapeHtml(data.description) + '</p>';
870
+ }
871
+ html += '</div>';
872
+
873
+ // Body
874
+ if (data.items && data.items.length > 0) {
875
+ html += '<div class="tx-confirm-body">';
876
+ data.items.forEach(function(item) {
877
+ html += '<div class="tx-confirm-row">' +
878
+ '<span class="tx-confirm-label">' + escapeHtml(item.label) + '</span>' +
879
+ '<span class="tx-confirm-value">' + escapeHtml(item.value) + '</span>' +
880
+ '</div>';
881
+ });
882
+ html += '</div>';
883
+ }
884
+
885
+ // Warning
886
+ if (data.warning) {
887
+ html += '<div class="tx-confirm-warning">' +
888
+ '<span class="tx-confirm-warning-icon">⚠️</span>' +
889
+ '<span>' + escapeHtml(data.warning) + '</span>' +
890
+ '</div>';
891
+ }
892
+
893
+ // Actions
894
+ html += '<div class="tx-confirm-actions">';
895
+ if (data.actions?.cancel) {
896
+ html += '<button class="tx-btn tx-btn-secondary" onclick="handleCancel()">' + escapeHtml(data.actions.cancel) + '</button>';
897
+ }
898
+ html += '<button class="tx-btn tx-btn-primary" onclick="handleConfirm()">' + escapeHtml(data.actions?.confirm || 'Confirm') + '</button>';
899
+ html += '</div>';
900
+
901
+ html += '</div>';
902
+ return html;
903
+ }
904
+
905
+ /**
906
+ * Render Result Card
907
+ */
908
+ function renderResult(data) {
909
+ const status = data.status || (data.success ? 'success' : data.error ? 'error' : 'pending');
910
+ const icons = { success: '✓', error: '✗', pending: '⏳' };
911
+
912
+ let html = '<div class="tx-result">';
913
+
914
+ // Icon
915
+ html += '<div class="tx-result-icon ' + status + '">' + icons[status] + '</div>';
916
+
917
+ // Title
918
+ html += '<h3 class="tx-result-title">' + escapeHtml(data.title || (status === 'success' ? 'Success' : status === 'error' ? 'Error' : 'Processing')) + '</h3>';
919
+
920
+ // Message
921
+ if (data.message) {
922
+ html += '<p class="tx-result-message">' + escapeHtml(data.message) + '</p>';
923
+ }
924
+
925
+ // Details
926
+ if (data.details && data.details.length > 0) {
927
+ html += '<div class="tx-result-details">';
928
+ data.details.forEach(function(item) {
929
+ html += '<div class="tx-result-detail-row">' +
930
+ '<span class="tx-result-detail-label">' + escapeHtml(item.label) + '</span>' +
931
+ '<span class="tx-result-detail-value">' + escapeHtml(item.value) + '</span>' +
932
+ '</div>';
933
+ });
934
+ html += '</div>';
935
+ }
936
+
937
+ // Actions
938
+ if (data.actions && data.actions.length > 0) {
939
+ html += '<div class="tx-result-actions">';
940
+ data.actions.forEach(function(action, i) {
941
+ const btnClass = i === 0 ? 'tx-btn-primary' : 'tx-btn-secondary';
942
+ html += '<button class="tx-btn ' + btnClass + '" onclick="handleAction(\\'' + escapeHtml(action.action) + '\\')">' + escapeHtml(action.label) + '</button>';
943
+ });
944
+ html += '</div>';
945
+ }
946
+
947
+ html += '</div>';
948
+ return html;
949
+ }
950
+
951
+ /**
952
+ * Render Empty State
953
+ */
954
+ function renderEmpty(message) {
955
+ return '<div class="tx-empty">' +
956
+ '<div class="tx-empty-icon">📭</div>' +
957
+ '<div class="tx-empty-title">Nothing here</div>' +
958
+ '<div class="tx-empty-message">' + escapeHtml(message || 'No items available') + '</div>' +
959
+ '</div>';
960
+ }
961
+
962
+ /**
963
+ * Render Loading State
964
+ */
965
+ function renderLoading(message) {
966
+ return '<div class="tx-loading">' +
967
+ '<div class="tx-spinner"></div>' +
968
+ '<div class="tx-loading-text">' + escapeHtml(message || 'Loading...') + '</div>' +
969
+ '</div>';
970
+ }
971
+
972
+ /**
973
+ * Format value for display
974
+ */
975
+ function formatValue(value) {
976
+ if (typeof value === 'number') {
977
+ // Check if it looks like currency
978
+ if (value > 0 && value < 100000) {
979
+ return '$' + value.toFixed(2);
980
+ }
981
+ return value.toLocaleString();
982
+ }
983
+ return String(value);
984
+ }
985
+
986
+ /**
987
+ * Get badge class based on value
988
+ */
989
+ function getBadgeClass(value) {
990
+ const lower = String(value).toLowerCase();
991
+ if (/success|complete|active|available|yes|true|done/.test(lower)) return 'tx-badge-success';
992
+ if (/error|failed|inactive|unavailable|no|false/.test(lower)) return 'tx-badge-error';
993
+ if (/pending|warning|waiting|processing/.test(lower)) return 'tx-badge-warning';
994
+ return 'tx-badge-neutral';
995
+ }
996
+
997
+ /**
998
+ * Handle selection
999
+ */
1000
+ function handleSelect(element) {
1001
+ // Remove previous selection
1002
+ document.querySelectorAll('.tx-select-card.selected, .tx-select-item.selected').forEach(function(el) {
1003
+ el.classList.remove('selected');
1004
+ });
1005
+
1006
+ // Add selection
1007
+ element.classList.add('selected');
1008
+
1009
+ // Get selected ID
1010
+ const id = element.dataset.id;
1011
+ console.log('Selected:', id);
1012
+
1013
+ // Dispatch event for parent to handle
1014
+ window.dispatchEvent(new CustomEvent('photon-select', { detail: { id: id } }));
1015
+ }
1016
+
1017
+ /**
1018
+ * Handle confirm action
1019
+ */
1020
+ function handleConfirm() {
1021
+ window.dispatchEvent(new CustomEvent('photon-confirm'));
1022
+ }
1023
+
1024
+ /**
1025
+ * Handle cancel action
1026
+ */
1027
+ function handleCancel() {
1028
+ window.dispatchEvent(new CustomEvent('photon-cancel'));
1029
+ }
1030
+
1031
+ /**
1032
+ * Handle custom action
1033
+ */
1034
+ function handleAction(action) {
1035
+ window.dispatchEvent(new CustomEvent('photon-action', { detail: { action: action } }));
1036
+ }
1037
+ `;
1038
+ }