@portel/photon-core 2.4.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 (89) hide show
  1. package/dist/asset-discovery.d.ts.map +1 -1
  2. package/dist/asset-discovery.js +2 -1
  3. package/dist/asset-discovery.js.map +1 -1
  4. package/dist/base.d.ts +6 -0
  5. package/dist/base.d.ts.map +1 -1
  6. package/dist/base.js +11 -1
  7. package/dist/base.js.map +1 -1
  8. package/dist/collections/ReactiveArray.d.ts +97 -0
  9. package/dist/collections/ReactiveArray.d.ts.map +1 -0
  10. package/dist/collections/ReactiveArray.js +158 -0
  11. package/dist/collections/ReactiveArray.js.map +1 -0
  12. package/dist/collections/ReactiveMap.d.ts +50 -0
  13. package/dist/collections/ReactiveMap.d.ts.map +1 -0
  14. package/dist/collections/ReactiveMap.js +71 -0
  15. package/dist/collections/ReactiveMap.js.map +1 -0
  16. package/dist/collections/ReactiveSet.d.ts +50 -0
  17. package/dist/collections/ReactiveSet.d.ts.map +1 -0
  18. package/dist/collections/ReactiveSet.js +71 -0
  19. package/dist/collections/ReactiveSet.js.map +1 -0
  20. package/dist/collections/index.d.ts +44 -0
  21. package/dist/collections/index.d.ts.map +1 -0
  22. package/dist/collections/index.js +44 -0
  23. package/dist/collections/index.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +16 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/types.d.ts +2 -0
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/ui-types/Cards.d.ts +139 -0
  31. package/dist/ui-types/Cards.d.ts.map +1 -0
  32. package/dist/ui-types/Cards.js +235 -0
  33. package/dist/ui-types/Cards.js.map +1 -0
  34. package/dist/ui-types/Chart.d.ts +136 -0
  35. package/dist/ui-types/Chart.d.ts.map +1 -0
  36. package/dist/ui-types/Chart.js +188 -0
  37. package/dist/ui-types/Chart.js.map +1 -0
  38. package/dist/ui-types/Field.d.ts +342 -0
  39. package/dist/ui-types/Field.d.ts.map +1 -0
  40. package/dist/ui-types/Field.js +200 -0
  41. package/dist/ui-types/Field.js.map +1 -0
  42. package/dist/ui-types/FieldRenderer.d.ts +32 -0
  43. package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
  44. package/dist/ui-types/FieldRenderer.js +277 -0
  45. package/dist/ui-types/FieldRenderer.js.map +1 -0
  46. package/dist/ui-types/Form.d.ts +212 -0
  47. package/dist/ui-types/Form.d.ts.map +1 -0
  48. package/dist/ui-types/Form.js +278 -0
  49. package/dist/ui-types/Form.js.map +1 -0
  50. package/dist/ui-types/Progress.d.ts +130 -0
  51. package/dist/ui-types/Progress.d.ts.map +1 -0
  52. package/dist/ui-types/Progress.js +191 -0
  53. package/dist/ui-types/Progress.js.map +1 -0
  54. package/dist/ui-types/Stats.d.ts +108 -0
  55. package/dist/ui-types/Stats.d.ts.map +1 -0
  56. package/dist/ui-types/Stats.js +162 -0
  57. package/dist/ui-types/Stats.js.map +1 -0
  58. package/dist/ui-types/Table.d.ts +206 -0
  59. package/dist/ui-types/Table.d.ts.map +1 -0
  60. package/dist/ui-types/Table.js +367 -0
  61. package/dist/ui-types/Table.js.map +1 -0
  62. package/dist/ui-types/base.d.ts +17 -0
  63. package/dist/ui-types/base.d.ts.map +1 -0
  64. package/dist/ui-types/base.js +18 -0
  65. package/dist/ui-types/base.js.map +1 -0
  66. package/dist/ui-types/index.d.ts +42 -0
  67. package/dist/ui-types/index.d.ts.map +1 -0
  68. package/dist/ui-types/index.js +50 -0
  69. package/dist/ui-types/index.js.map +1 -0
  70. package/package.json +2 -2
  71. package/src/asset-discovery.ts +2 -1
  72. package/src/base.ts +13 -1
  73. package/src/collections/ReactiveArray.ts +179 -0
  74. package/src/collections/ReactiveMap.ts +81 -0
  75. package/src/collections/ReactiveSet.ts +81 -0
  76. package/src/collections/index.ts +44 -0
  77. package/src/index.ts +75 -0
  78. package/src/types.ts +2 -0
  79. package/src/ui-types/Cards.ts +286 -0
  80. package/src/ui-types/Chart.ts +239 -0
  81. package/src/ui-types/Field.ts +594 -0
  82. package/src/ui-types/FieldRenderer.ts +364 -0
  83. package/src/ui-types/Form.ts +363 -0
  84. package/src/ui-types/Progress.ts +237 -0
  85. package/src/ui-types/Stats.ts +204 -0
  86. package/src/ui-types/Table.ts +438 -0
  87. package/src/ui-types/base.ts +25 -0
  88. package/src/ui-types/index.ts +96 -0
  89. package/src/ui-types/ui-types.test.ts +444 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * ReactiveArray - A managed array that auto-emits events on mutations
3
+ *
4
+ * When used as a class property, automatically emits events when items are
5
+ * added, removed, or updated.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { ReactiveArray } from '@portel/photon-core';
10
+ *
11
+ * export default class TodoList {
12
+ * items = ReactiveArray.create<Task>('items', (event, data) => this.emit(event, data));
13
+ *
14
+ * add(text: string) {
15
+ * this.items.push({ id: crypto.randomUUID(), text });
16
+ * // Auto-emits 'items:added' with the new item
17
+ * }
18
+ * }
19
+ * ```
20
+ *
21
+ * Event naming convention (inspired by Firebase):
22
+ * - `{prop}:added` - Item pushed/inserted
23
+ * - `{prop}:removed` - Item popped/spliced out
24
+ * - `{prop}:updated` - Item at index changed
25
+ * - `{prop}:changed` - Full array replaced
26
+ */
27
+
28
+ export type Emitter = (event: string, data: unknown) => void;
29
+
30
+ export class ReactiveArray<T> extends Array<T> {
31
+ private _propertyName: string = '';
32
+ private _emitter: Emitter = () => {};
33
+
34
+ /**
35
+ * Create a new ReactiveArray bound to a property name and emitter function.
36
+ * The emitter is called with event names like 'items:added', 'items:removed', etc.
37
+ */
38
+ static create<T>(
39
+ propertyName: string,
40
+ emitter: Emitter,
41
+ initialItems?: T[]
42
+ ): ReactiveArray<T> {
43
+ const arr = new ReactiveArray<T>();
44
+ arr._propertyName = propertyName;
45
+ arr._emitter = emitter;
46
+ if (initialItems) {
47
+ // Use super.push to avoid triggering events during init
48
+ globalThis.Array.prototype.push.apply(arr, initialItems);
49
+ }
50
+ return arr;
51
+ }
52
+
53
+ /**
54
+ * Add one or more elements to the end of the array.
55
+ * Emits `{prop}:added` for each item.
56
+ */
57
+ push(...items: T[]): number {
58
+ const result = super.push(...items);
59
+ items.forEach((item) => this._emitter(`${this._propertyName}:added`, item));
60
+ return result;
61
+ }
62
+
63
+ /**
64
+ * Remove the last element from the array.
65
+ * Emits `{prop}:removed` with the removed item.
66
+ */
67
+ pop(): T | undefined {
68
+ const item = super.pop();
69
+ if (item !== undefined) {
70
+ this._emitter(`${this._propertyName}:removed`, item);
71
+ }
72
+ return item;
73
+ }
74
+
75
+ /**
76
+ * Remove the first element from the array.
77
+ * Emits `{prop}:removed` with the removed item.
78
+ */
79
+ shift(): T | undefined {
80
+ const item = super.shift();
81
+ if (item !== undefined) {
82
+ this._emitter(`${this._propertyName}:removed`, item);
83
+ }
84
+ return item;
85
+ }
86
+
87
+ /**
88
+ * Add one or more elements to the beginning of the array.
89
+ * Emits `{prop}:added` for each item.
90
+ */
91
+ unshift(...items: T[]): number {
92
+ const result = super.unshift(...items);
93
+ items.forEach((item) => this._emitter(`${this._propertyName}:added`, item));
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Remove/replace elements from the array.
99
+ * Emits `{prop}:removed` for deleted items and `{prop}:added` for inserted items.
100
+ */
101
+ splice(start: number, deleteCount?: number, ...items: T[]): T[] {
102
+ const removed = super.splice(start, deleteCount ?? 0, ...items);
103
+ removed.forEach((item) =>
104
+ this._emitter(`${this._propertyName}:removed`, item)
105
+ );
106
+ items.forEach((item) => this._emitter(`${this._propertyName}:added`, item));
107
+ return removed;
108
+ }
109
+
110
+ /**
111
+ * Set an element at a specific index.
112
+ * Emits `{prop}:updated` with `{ index, value, previous }`.
113
+ */
114
+ set(index: number, value: T): void {
115
+ const previous = this[index];
116
+ this[index] = value;
117
+ this._emitter(`${this._propertyName}:updated`, { index, value, previous });
118
+ }
119
+
120
+ /**
121
+ * Replace all items in the array.
122
+ * Emits `{prop}:changed` with the full new array.
123
+ */
124
+ replaceAll(items: T[]): void {
125
+ // Clear and replace
126
+ this.length = 0;
127
+ globalThis.Array.prototype.push.apply(this, items);
128
+ this._emitter(`${this._propertyName}:changed`, [...items]);
129
+ }
130
+
131
+ /**
132
+ * Clear all items from the array.
133
+ * Emits `{prop}:changed` with an empty array.
134
+ */
135
+ clear(): void {
136
+ this.length = 0;
137
+ this._emitter(`${this._propertyName}:changed`, []);
138
+ }
139
+
140
+ /**
141
+ * Sort the array in place.
142
+ * Emits `{prop}:changed` with the sorted array.
143
+ */
144
+ sort(compareFn?: (a: T, b: T) => number): this {
145
+ super.sort(compareFn);
146
+ this._emitter(`${this._propertyName}:changed`, [...this]);
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ * Reverse the array in place.
152
+ * Emits `{prop}:changed` with the reversed array.
153
+ */
154
+ reverse(): this {
155
+ super.reverse();
156
+ this._emitter(`${this._propertyName}:changed`, [...this]);
157
+ return this;
158
+ }
159
+
160
+ /**
161
+ * Fill the array with a value.
162
+ * Emits `{prop}:changed` with the filled array.
163
+ */
164
+ fill(value: T, start?: number, end?: number): this {
165
+ super.fill(value, start, end);
166
+ this._emitter(`${this._propertyName}:changed`, [...this]);
167
+ return this;
168
+ }
169
+
170
+ /**
171
+ * Copy items within the array.
172
+ * Emits `{prop}:changed` with the modified array.
173
+ */
174
+ copyWithin(target: number, start: number, end?: number): this {
175
+ super.copyWithin(target, start, end);
176
+ this._emitter(`${this._propertyName}:changed`, [...this]);
177
+ return this;
178
+ }
179
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * ReactiveMap - A managed Map that auto-emits events on mutations
3
+ *
4
+ * When used as a class property, automatically emits events when entries are
5
+ * set, deleted, or the map is cleared.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { ReactiveMap } from '@portel/photon-core';
10
+ *
11
+ * export default class Cache {
12
+ * data = ReactiveMap.create<string, any>('data', (event, data) => this.emit(event, data));
13
+ *
14
+ * set(key: string, value: any) {
15
+ * this.data.set(key, value);
16
+ * // Auto-emits 'data:set' with { key, value }
17
+ * }
18
+ * }
19
+ * ```
20
+ *
21
+ * Event naming convention:
22
+ * - `{prop}:set` - Entry set (new or updated)
23
+ * - `{prop}:deleted` - Entry deleted
24
+ * - `{prop}:cleared` - All entries cleared
25
+ */
26
+
27
+ import { Emitter } from './ReactiveArray.js';
28
+
29
+ export class ReactiveMap<K, V> extends Map<K, V> {
30
+ private _propertyName: string = '';
31
+ private _emitter: Emitter = () => {};
32
+
33
+ /**
34
+ * Create a new ReactiveMap bound to a property name and emitter function.
35
+ */
36
+ static create<K, V>(
37
+ propertyName: string,
38
+ emitter: Emitter,
39
+ initialEntries?: Iterable<[K, V]>
40
+ ): ReactiveMap<K, V> {
41
+ const map = new ReactiveMap<K, V>(initialEntries);
42
+ map._propertyName = propertyName;
43
+ map._emitter = emitter;
44
+ return map;
45
+ }
46
+
47
+ /**
48
+ * Set a value for a key.
49
+ * Emits `{prop}:set` with `{ key, value, isNew }`.
50
+ */
51
+ set(key: K, value: V): this {
52
+ const isNew = !super.has(key);
53
+ const previous = super.get(key);
54
+ super.set(key, value);
55
+ this._emitter(`${this._propertyName}:set`, { key, value, isNew, previous });
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * Delete an entry by key.
61
+ * Emits `{prop}:deleted` with `{ key, value }` if the key existed.
62
+ */
63
+ delete(key: K): boolean {
64
+ const value = super.get(key);
65
+ const existed = super.delete(key);
66
+ if (existed) {
67
+ this._emitter(`${this._propertyName}:deleted`, { key, value });
68
+ }
69
+ return existed;
70
+ }
71
+
72
+ /**
73
+ * Clear all entries from the map.
74
+ * Emits `{prop}:cleared` with the count of removed entries.
75
+ */
76
+ clear(): void {
77
+ const count = super.size;
78
+ super.clear();
79
+ this._emitter(`${this._propertyName}:cleared`, { count });
80
+ }
81
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * ReactiveSet - A managed Set that auto-emits events on mutations
3
+ *
4
+ * When used as a class property, automatically emits events when values are
5
+ * added, deleted, or the set is cleared.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { ReactiveSet } from '@portel/photon-core';
10
+ *
11
+ * export default class Tags {
12
+ * tags = ReactiveSet.create<string>('tags', (event, data) => this.emit(event, data));
13
+ *
14
+ * addTag(tag: string) {
15
+ * this.tags.add(tag);
16
+ * // Auto-emits 'tags:added' with the tag
17
+ * }
18
+ * }
19
+ * ```
20
+ *
21
+ * Event naming convention:
22
+ * - `{prop}:added` - Value added (if not already present)
23
+ * - `{prop}:deleted` - Value deleted
24
+ * - `{prop}:cleared` - All values cleared
25
+ */
26
+
27
+ import { Emitter } from './ReactiveArray.js';
28
+
29
+ export class ReactiveSet<T> extends Set<T> {
30
+ private _propertyName: string = '';
31
+ private _emitter: Emitter = () => {};
32
+
33
+ /**
34
+ * Create a new ReactiveSet bound to a property name and emitter function.
35
+ */
36
+ static create<T>(
37
+ propertyName: string,
38
+ emitter: Emitter,
39
+ initialValues?: Iterable<T>
40
+ ): ReactiveSet<T> {
41
+ const set = new ReactiveSet<T>(initialValues);
42
+ set._propertyName = propertyName;
43
+ set._emitter = emitter;
44
+ return set;
45
+ }
46
+
47
+ /**
48
+ * Add a value to the set.
49
+ * Emits `{prop}:added` with the value if it wasn't already present.
50
+ */
51
+ add(value: T): this {
52
+ const isNew = !super.has(value);
53
+ super.add(value);
54
+ if (isNew) {
55
+ this._emitter(`${this._propertyName}:added`, value);
56
+ }
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Delete a value from the set.
62
+ * Emits `{prop}:deleted` with the value if it existed.
63
+ */
64
+ delete(value: T): boolean {
65
+ const existed = super.delete(value);
66
+ if (existed) {
67
+ this._emitter(`${this._propertyName}:deleted`, value);
68
+ }
69
+ return existed;
70
+ }
71
+
72
+ /**
73
+ * Clear all values from the set.
74
+ * Emits `{prop}:cleared` with the count of removed values.
75
+ */
76
+ clear(): void {
77
+ const count = super.size;
78
+ super.clear();
79
+ this._emitter(`${this._propertyName}:cleared`, { count });
80
+ }
81
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Managed Collections - Auto-emit events on mutations
3
+ *
4
+ * These collections extend the native JavaScript collection types and automatically
5
+ * emit events when their contents change. This enables seamless real-time sync
6
+ * between server and client.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { ReactiveArray } from '@portel/photon-core';
11
+ *
12
+ * export default class TodoList {
13
+ * items = ReactiveArray.create<Task>('items', (event, data) => this.emit(event, data));
14
+ *
15
+ * add(text: string) {
16
+ * this.items.push({ id: crypto.randomUUID(), text });
17
+ * // Auto-emits 'items:added' - UI updates automatically!
18
+ * }
19
+ * }
20
+ * ```
21
+ *
22
+ * On the client:
23
+ * ```javascript
24
+ * todoList.onItemsAdded((item) => {
25
+ * // Auto UI handles this - item appears with animation
26
+ * });
27
+ * ```
28
+ *
29
+ * Event naming convention (inspired by Firebase):
30
+ * | Operation | Event Name | Data |
31
+ * |---------------------|---------------------|-------------------------|
32
+ * | Array.push(item) | `{prop}:added` | The item |
33
+ * | Array.splice(i, 1) | `{prop}:removed` | The removed item |
34
+ * | Array.set(i, x) | `{prop}:updated` | { index, value, prev } |
35
+ * | Array.replaceAll() | `{prop}:changed` | All items |
36
+ * | Map.set(k, v) | `{prop}:set` | { key, value, isNew } |
37
+ * | Map.delete(k) | `{prop}:deleted` | { key, value } |
38
+ * | Set.add(v) | `{prop}:added` | The value |
39
+ * | Set.delete(v) | `{prop}:deleted` | The value |
40
+ */
41
+
42
+ export { ReactiveArray, type Emitter } from './ReactiveArray.js';
43
+ export { ReactiveMap } from './ReactiveMap.js';
44
+ export { ReactiveSet } from './ReactiveSet.js';
package/src/index.ts CHANGED
@@ -440,3 +440,78 @@ export {
440
440
  discoverAssets,
441
441
  autoDiscoverAssets,
442
442
  } from './asset-discovery.js';
443
+
444
+ // ===== MANAGED COLLECTIONS =====
445
+ // Auto-emit events on mutations for seamless real-time sync
446
+ export {
447
+ ReactiveArray,
448
+ ReactiveMap,
449
+ ReactiveSet,
450
+ type Emitter,
451
+ } from './collections/index.js';
452
+
453
+ // ===== PURPOSE-DRIVEN UI TYPES =====
454
+ // Polymorphic return types that auto-render with appropriate UI
455
+ export {
456
+ // Base
457
+ PhotonUIType,
458
+ isPhotonUIType,
459
+
460
+ // Field System (React Admin-style)
461
+ Field,
462
+ type FieldDefinition,
463
+ type FieldType,
464
+ type FieldAlignment,
465
+ type BaseFieldOptions,
466
+ type TextFieldOptions,
467
+ type NumberFieldOptions,
468
+ type CurrencyFieldOptions,
469
+ type DateFieldOptions,
470
+ type DateFormat,
471
+ type ImageFieldOptions,
472
+ type BadgeFieldOptions,
473
+ type RatingFieldOptions,
474
+ type PriceFieldOptions,
475
+ type StockFieldOptions,
476
+ type ActionItem,
477
+ type ActionsFieldOptions,
478
+ type RenderFunction,
479
+ getFieldValue,
480
+ formatFieldLabel,
481
+ interpolateTemplate,
482
+ renderFieldToText,
483
+ renderFieldToStructured,
484
+ type RenderedField,
485
+
486
+ // Data Display
487
+ Table,
488
+ type TableColumn,
489
+ type TableOptions,
490
+ type ColumnType,
491
+ Cards,
492
+ type CardFieldMapping,
493
+ type CardsOptions,
494
+
495
+ // Visualization
496
+ Chart,
497
+ type ChartType,
498
+ type ChartSeries,
499
+ type ChartDataPoint,
500
+ type ChartOptions,
501
+ Stats,
502
+ type StatItem,
503
+ type StatFormat,
504
+ type StatsOptions,
505
+ Progress,
506
+ type ProgressBar,
507
+ type ProgressStep,
508
+ type ProgressStyle,
509
+ type StepStatus,
510
+ type ProgressOptions,
511
+
512
+ // Interactive
513
+ Form,
514
+ type FormField,
515
+ type FieldType as FormFieldType,
516
+ type FormOptions,
517
+ } from './ui-types/index.js';
package/src/types.ts CHANGED
@@ -373,6 +373,8 @@ export interface PhotonMCPClassExtended extends PhotonMCPClass {
373
373
  statics: StaticInfo[];
374
374
  /** Assets from the Photon's asset folder (UI, prompts, resources) */
375
375
  assets?: PhotonAssets;
376
+ /** Names of injected @photon dependencies (for client-side event routing) */
377
+ injectedPhotons?: string[];
376
378
  }
377
379
 
378
380
  // ══════════════════════════════════════════════════════════════════════════════