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