@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.
- package/dist/asset-discovery.d.ts.map +1 -1
- package/dist/asset-discovery.js +2 -1
- package/dist/asset-discovery.js.map +1 -1
- package/dist/base.d.ts +6 -0
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +11 -1
- package/dist/base.js.map +1 -1
- package/dist/collections/ReactiveArray.d.ts +97 -0
- package/dist/collections/ReactiveArray.d.ts.map +1 -0
- package/dist/collections/ReactiveArray.js +158 -0
- package/dist/collections/ReactiveArray.js.map +1 -0
- package/dist/collections/ReactiveMap.d.ts +50 -0
- package/dist/collections/ReactiveMap.d.ts.map +1 -0
- package/dist/collections/ReactiveMap.js +71 -0
- package/dist/collections/ReactiveMap.js.map +1 -0
- package/dist/collections/ReactiveSet.d.ts +50 -0
- package/dist/collections/ReactiveSet.d.ts.map +1 -0
- package/dist/collections/ReactiveSet.js +71 -0
- package/dist/collections/ReactiveSet.js.map +1 -0
- package/dist/collections/index.d.ts +44 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +44 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui-types/Cards.d.ts +139 -0
- package/dist/ui-types/Cards.d.ts.map +1 -0
- package/dist/ui-types/Cards.js +235 -0
- package/dist/ui-types/Cards.js.map +1 -0
- package/dist/ui-types/Chart.d.ts +136 -0
- package/dist/ui-types/Chart.d.ts.map +1 -0
- package/dist/ui-types/Chart.js +188 -0
- package/dist/ui-types/Chart.js.map +1 -0
- package/dist/ui-types/Field.d.ts +342 -0
- package/dist/ui-types/Field.d.ts.map +1 -0
- package/dist/ui-types/Field.js +200 -0
- package/dist/ui-types/Field.js.map +1 -0
- package/dist/ui-types/FieldRenderer.d.ts +32 -0
- package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
- package/dist/ui-types/FieldRenderer.js +277 -0
- package/dist/ui-types/FieldRenderer.js.map +1 -0
- package/dist/ui-types/Form.d.ts +212 -0
- package/dist/ui-types/Form.d.ts.map +1 -0
- package/dist/ui-types/Form.js +278 -0
- package/dist/ui-types/Form.js.map +1 -0
- package/dist/ui-types/Progress.d.ts +130 -0
- package/dist/ui-types/Progress.d.ts.map +1 -0
- package/dist/ui-types/Progress.js +191 -0
- package/dist/ui-types/Progress.js.map +1 -0
- package/dist/ui-types/Stats.d.ts +108 -0
- package/dist/ui-types/Stats.d.ts.map +1 -0
- package/dist/ui-types/Stats.js +162 -0
- package/dist/ui-types/Stats.js.map +1 -0
- package/dist/ui-types/Table.d.ts +206 -0
- package/dist/ui-types/Table.d.ts.map +1 -0
- package/dist/ui-types/Table.js +367 -0
- package/dist/ui-types/Table.js.map +1 -0
- package/dist/ui-types/base.d.ts +17 -0
- package/dist/ui-types/base.d.ts.map +1 -0
- package/dist/ui-types/base.js +18 -0
- package/dist/ui-types/base.js.map +1 -0
- package/dist/ui-types/index.d.ts +42 -0
- package/dist/ui-types/index.d.ts.map +1 -0
- package/dist/ui-types/index.js +50 -0
- package/dist/ui-types/index.js.map +1 -0
- package/package.json +2 -2
- package/src/asset-discovery.ts +2 -1
- package/src/base.ts +13 -1
- package/src/collections/ReactiveArray.ts +179 -0
- package/src/collections/ReactiveMap.ts +81 -0
- package/src/collections/ReactiveSet.ts +81 -0
- package/src/collections/index.ts +44 -0
- package/src/index.ts +75 -0
- package/src/types.ts +2 -0
- package/src/ui-types/Cards.ts +286 -0
- package/src/ui-types/Chart.ts +239 -0
- package/src/ui-types/Field.ts +594 -0
- package/src/ui-types/FieldRenderer.ts +364 -0
- package/src/ui-types/Form.ts +363 -0
- package/src/ui-types/Progress.ts +237 -0
- package/src/ui-types/Stats.ts +204 -0
- package/src/ui-types/Table.ts +438 -0
- package/src/ui-types/base.ts +25 -0
- package/src/ui-types/index.ts +96 -0
- package/src/ui-types/ui-types.test.ts +444 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field - Unified field system for UI types
|
|
3
|
+
*
|
|
4
|
+
* Fields define how data is displayed. They work across Table, Cards, List, etc.
|
|
5
|
+
* Each field type handles its own rendering logic and formatting.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Field, Table, Cards } from '@portel/photon-core';
|
|
10
|
+
*
|
|
11
|
+
* // Define fields once, use anywhere
|
|
12
|
+
* const productFields = [
|
|
13
|
+
* Field.image('thumbnail', { width: 80, rounded: true }),
|
|
14
|
+
* Field.text('name', { link: '/products/{id}' }),
|
|
15
|
+
* Field.price('price', { original: 'msrp', currency: 'USD' }),
|
|
16
|
+
* Field.rating('rating', { count: 'reviewCount' }),
|
|
17
|
+
* Field.badge('status', { colors: { active: 'green' } }),
|
|
18
|
+
* Field.actions([{ label: 'Edit', method: 'edit' }]),
|
|
19
|
+
* ];
|
|
20
|
+
*
|
|
21
|
+
* // Works in Table
|
|
22
|
+
* new Table().fields(productFields).rows(data);
|
|
23
|
+
*
|
|
24
|
+
* // Works in Cards
|
|
25
|
+
* new Cards().fields(productFields).items(data);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
// Common Types
|
|
31
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
|
|
33
|
+
export type FieldAlignment = 'left' | 'center' | 'right';
|
|
34
|
+
|
|
35
|
+
export interface BaseFieldOptions {
|
|
36
|
+
/** Display label (auto-inferred from source if not provided) */
|
|
37
|
+
label?: string;
|
|
38
|
+
/** Enable sorting on this field */
|
|
39
|
+
sortable?: boolean;
|
|
40
|
+
/** Sort by different field */
|
|
41
|
+
sortBy?: string;
|
|
42
|
+
/** Text alignment */
|
|
43
|
+
align?: FieldAlignment;
|
|
44
|
+
/** Column width (CSS value like '100px', '20%') */
|
|
45
|
+
columnWidth?: string;
|
|
46
|
+
/** Text to show when value is empty/null */
|
|
47
|
+
emptyText?: string;
|
|
48
|
+
/** Hide this field */
|
|
49
|
+
hidden?: boolean;
|
|
50
|
+
/** Additional CSS class */
|
|
51
|
+
className?: string;
|
|
52
|
+
/** Tooltip text */
|
|
53
|
+
tooltip?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
57
|
+
// Text Fields
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
export interface TextFieldOptions extends BaseFieldOptions {
|
|
61
|
+
/** Make text a link. Use {field} for interpolation */
|
|
62
|
+
link?: string;
|
|
63
|
+
/** Open link in new tab */
|
|
64
|
+
external?: boolean;
|
|
65
|
+
/** Truncate text to max characters */
|
|
66
|
+
truncate?: number;
|
|
67
|
+
/** Text variant */
|
|
68
|
+
variant?: 'heading' | 'body' | 'caption' | 'code';
|
|
69
|
+
/** Make text copyable */
|
|
70
|
+
copyable?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface EmailFieldOptions extends BaseFieldOptions {
|
|
74
|
+
/** Show as mailto link */
|
|
75
|
+
linked?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface UrlFieldOptions extends BaseFieldOptions {
|
|
79
|
+
/** Display text (otherwise shows URL) */
|
|
80
|
+
text?: string;
|
|
81
|
+
/** Truncate URL display */
|
|
82
|
+
truncate?: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface PhoneFieldOptions extends BaseFieldOptions {
|
|
86
|
+
/** Show as tel: link */
|
|
87
|
+
linked?: boolean;
|
|
88
|
+
/** Format pattern */
|
|
89
|
+
format?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
93
|
+
// Numeric Fields
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
export interface NumberFieldOptions extends BaseFieldOptions {
|
|
97
|
+
/** Decimal places */
|
|
98
|
+
decimals?: number;
|
|
99
|
+
/** Use compact notation (1.2K, 5M) */
|
|
100
|
+
compact?: boolean;
|
|
101
|
+
/** Prefix text */
|
|
102
|
+
prefix?: string;
|
|
103
|
+
/** Suffix text */
|
|
104
|
+
suffix?: string;
|
|
105
|
+
/** Locale for formatting */
|
|
106
|
+
locale?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CurrencyFieldOptions extends BaseFieldOptions {
|
|
110
|
+
/** Currency code (USD, EUR, etc.) */
|
|
111
|
+
currency?: string;
|
|
112
|
+
/** Locale for formatting */
|
|
113
|
+
locale?: string;
|
|
114
|
+
/** Show currency symbol */
|
|
115
|
+
showSymbol?: boolean;
|
|
116
|
+
/** Decimal places */
|
|
117
|
+
decimals?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface PercentFieldOptions extends BaseFieldOptions {
|
|
121
|
+
/** Decimal places */
|
|
122
|
+
decimals?: number;
|
|
123
|
+
/** Multiply by 100 (if value is 0.5 for 50%) */
|
|
124
|
+
multiply?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
128
|
+
// Date Fields
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
130
|
+
|
|
131
|
+
export type DateFormat = 'relative' | 'short' | 'medium' | 'long' | 'iso' | string;
|
|
132
|
+
|
|
133
|
+
export interface DateFieldOptions extends BaseFieldOptions {
|
|
134
|
+
/** Date format */
|
|
135
|
+
format?: DateFormat;
|
|
136
|
+
/** Show time component */
|
|
137
|
+
showTime?: boolean;
|
|
138
|
+
/** Locale for formatting */
|
|
139
|
+
locale?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface TimeFieldOptions extends BaseFieldOptions {
|
|
143
|
+
/** Time format */
|
|
144
|
+
format?: '12h' | '24h';
|
|
145
|
+
/** Show seconds */
|
|
146
|
+
showSeconds?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface DateRangeFieldOptions extends BaseFieldOptions {
|
|
150
|
+
/** End date source */
|
|
151
|
+
endSource: string;
|
|
152
|
+
/** Format for dates */
|
|
153
|
+
format?: DateFormat;
|
|
154
|
+
/** Separator between dates */
|
|
155
|
+
separator?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
159
|
+
// Boolean Fields
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
161
|
+
|
|
162
|
+
export interface BooleanFieldOptions extends BaseFieldOptions {
|
|
163
|
+
/** Label for true value */
|
|
164
|
+
trueLabel?: string;
|
|
165
|
+
/** Label for false value */
|
|
166
|
+
falseLabel?: string;
|
|
167
|
+
/** Icon for true value */
|
|
168
|
+
trueIcon?: string;
|
|
169
|
+
/** Icon for false value */
|
|
170
|
+
falseIcon?: string;
|
|
171
|
+
/** Use colored badges instead of icons */
|
|
172
|
+
asBadge?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
176
|
+
// Media Fields
|
|
177
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
178
|
+
|
|
179
|
+
export interface ImageFieldOptions extends BaseFieldOptions {
|
|
180
|
+
/** Image width */
|
|
181
|
+
width?: number;
|
|
182
|
+
/** Image height */
|
|
183
|
+
height?: number;
|
|
184
|
+
/** Make image circular */
|
|
185
|
+
rounded?: boolean;
|
|
186
|
+
/** Fallback image URL */
|
|
187
|
+
fallback?: string;
|
|
188
|
+
/** Alt text source */
|
|
189
|
+
altSource?: string;
|
|
190
|
+
/** Enable lightbox on click */
|
|
191
|
+
lightbox?: boolean;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface AvatarFieldOptions extends BaseFieldOptions {
|
|
195
|
+
/** Size in pixels */
|
|
196
|
+
size?: number;
|
|
197
|
+
/** Source for name (for initials fallback) */
|
|
198
|
+
nameSource?: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface GalleryFieldOptions extends BaseFieldOptions {
|
|
202
|
+
/** Max images to show inline */
|
|
203
|
+
maxVisible?: number;
|
|
204
|
+
/** Thumbnail size */
|
|
205
|
+
thumbnailSize?: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface FileFieldOptions extends BaseFieldOptions {
|
|
209
|
+
/** Show file size */
|
|
210
|
+
showSize?: boolean;
|
|
211
|
+
/** Show file type icon */
|
|
212
|
+
showIcon?: boolean;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
216
|
+
// Status/Category Fields
|
|
217
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
218
|
+
|
|
219
|
+
export interface BadgeFieldOptions extends BaseFieldOptions {
|
|
220
|
+
/** Color mapping: { value: color } */
|
|
221
|
+
colors?: Record<string, string>;
|
|
222
|
+
/** Icon mapping: { value: icon } */
|
|
223
|
+
icons?: Record<string, string>;
|
|
224
|
+
/** Variant style */
|
|
225
|
+
variant?: 'solid' | 'outline' | 'subtle';
|
|
226
|
+
/** Size */
|
|
227
|
+
size?: 'sm' | 'md' | 'lg';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface TagsFieldOptions extends BaseFieldOptions {
|
|
231
|
+
/** Max tags to show */
|
|
232
|
+
max?: number;
|
|
233
|
+
/** Color for tags */
|
|
234
|
+
color?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
238
|
+
// Rating Fields
|
|
239
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
240
|
+
|
|
241
|
+
export interface RatingFieldOptions extends BaseFieldOptions {
|
|
242
|
+
/** Maximum rating value */
|
|
243
|
+
max?: number;
|
|
244
|
+
/** Source for review count */
|
|
245
|
+
countSource?: string;
|
|
246
|
+
/** Show numeric value */
|
|
247
|
+
showValue?: boolean;
|
|
248
|
+
/** Icon for filled star */
|
|
249
|
+
icon?: string;
|
|
250
|
+
/** Color for stars */
|
|
251
|
+
color?: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
255
|
+
// Commerce Fields
|
|
256
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
257
|
+
|
|
258
|
+
export interface PriceFieldOptions extends BaseFieldOptions {
|
|
259
|
+
/** Source for original/compare-at price */
|
|
260
|
+
originalSource?: string;
|
|
261
|
+
/** Currency code */
|
|
262
|
+
currency?: string;
|
|
263
|
+
/** Locale for formatting */
|
|
264
|
+
locale?: string;
|
|
265
|
+
/** Show discount percentage badge */
|
|
266
|
+
showDiscount?: boolean;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface StockFieldOptions extends BaseFieldOptions {
|
|
270
|
+
/** Label when in stock */
|
|
271
|
+
inStockLabel?: string;
|
|
272
|
+
/** Label when out of stock */
|
|
273
|
+
outOfStockLabel?: string;
|
|
274
|
+
/** Threshold for "low stock" warning */
|
|
275
|
+
lowStockThreshold?: number;
|
|
276
|
+
/** Label for low stock */
|
|
277
|
+
lowStockLabel?: string;
|
|
278
|
+
/** Show quantity */
|
|
279
|
+
showQuantity?: boolean;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export interface QuantityFieldOptions extends BaseFieldOptions {
|
|
283
|
+
/** Minimum value */
|
|
284
|
+
min?: number;
|
|
285
|
+
/** Maximum value */
|
|
286
|
+
max?: number;
|
|
287
|
+
/** Step increment */
|
|
288
|
+
step?: number;
|
|
289
|
+
/** Method to call on change */
|
|
290
|
+
onChange?: string;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
294
|
+
// Reference Fields
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
296
|
+
|
|
297
|
+
export interface UserFieldOptions extends BaseFieldOptions {
|
|
298
|
+
/** Source for avatar image */
|
|
299
|
+
avatarSource?: string;
|
|
300
|
+
/** Source for display name */
|
|
301
|
+
nameSource?: string;
|
|
302
|
+
/** Source for secondary text (email, role) */
|
|
303
|
+
secondarySource?: string;
|
|
304
|
+
/** Link template */
|
|
305
|
+
link?: string;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface ReferenceFieldOptions extends BaseFieldOptions {
|
|
309
|
+
/** Source for display text */
|
|
310
|
+
displaySource?: string;
|
|
311
|
+
/** Link template */
|
|
312
|
+
link?: string;
|
|
313
|
+
/** Resource type (for routing) */
|
|
314
|
+
resource?: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
318
|
+
// Action Fields
|
|
319
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
320
|
+
|
|
321
|
+
export interface ActionItem {
|
|
322
|
+
/** Button label */
|
|
323
|
+
label: string;
|
|
324
|
+
/** Photon method to call */
|
|
325
|
+
method: string;
|
|
326
|
+
/** Icon name */
|
|
327
|
+
icon?: string;
|
|
328
|
+
/** Button variant */
|
|
329
|
+
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
|
|
330
|
+
/** Require confirmation */
|
|
331
|
+
confirm?: boolean | string;
|
|
332
|
+
/** Confirmation message */
|
|
333
|
+
confirmMessage?: string;
|
|
334
|
+
/** Disable condition (field name that must be truthy to disable) */
|
|
335
|
+
disabledWhen?: string;
|
|
336
|
+
/** Hide condition */
|
|
337
|
+
hiddenWhen?: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface ActionsFieldOptions extends BaseFieldOptions {
|
|
341
|
+
/** Show as dropdown menu */
|
|
342
|
+
dropdown?: boolean;
|
|
343
|
+
/** Dropdown trigger label */
|
|
344
|
+
dropdownLabel?: string;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
348
|
+
// Custom Field
|
|
349
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
350
|
+
|
|
351
|
+
export type RenderFunction = (value: any, record: Record<string, any>) => string;
|
|
352
|
+
|
|
353
|
+
export interface CustomFieldOptions extends BaseFieldOptions {
|
|
354
|
+
/** Render function */
|
|
355
|
+
render: RenderFunction;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
359
|
+
// Field Definition
|
|
360
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
361
|
+
|
|
362
|
+
export type FieldType =
|
|
363
|
+
| 'text' | 'email' | 'url' | 'phone'
|
|
364
|
+
| 'number' | 'currency' | 'percent'
|
|
365
|
+
| 'date' | 'datetime' | 'time' | 'dateRange'
|
|
366
|
+
| 'boolean'
|
|
367
|
+
| 'image' | 'avatar' | 'gallery' | 'file'
|
|
368
|
+
| 'badge' | 'tags'
|
|
369
|
+
| 'rating'
|
|
370
|
+
| 'price' | 'stock' | 'quantity'
|
|
371
|
+
| 'user' | 'reference'
|
|
372
|
+
| 'actions'
|
|
373
|
+
| 'custom';
|
|
374
|
+
|
|
375
|
+
export interface FieldDefinition {
|
|
376
|
+
type: FieldType;
|
|
377
|
+
source: string;
|
|
378
|
+
options: Record<string, any>;
|
|
379
|
+
actions?: ActionItem[];
|
|
380
|
+
render?: RenderFunction;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
384
|
+
// Field Factory
|
|
385
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Field factory for creating field definitions
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* const fields = [
|
|
393
|
+
* Field.text('name'),
|
|
394
|
+
* Field.email('email'),
|
|
395
|
+
* Field.price('price', { currency: 'USD', originalSource: 'msrp' }),
|
|
396
|
+
* Field.badge('status', { colors: { active: 'green', inactive: 'gray' } }),
|
|
397
|
+
* Field.actions([{ label: 'Edit', method: 'edit' }]),
|
|
398
|
+
* ];
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
export const Field = {
|
|
402
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
403
|
+
// Text Fields
|
|
404
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
text(source: string, options?: TextFieldOptions): FieldDefinition {
|
|
407
|
+
return { type: 'text', source, options: options ?? {} };
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
email(source: string, options?: EmailFieldOptions): FieldDefinition {
|
|
411
|
+
return { type: 'email', source, options: { linked: true, ...options } };
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
url(source: string, options?: UrlFieldOptions): FieldDefinition {
|
|
415
|
+
return { type: 'url', source, options: options ?? {} };
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
phone(source: string, options?: PhoneFieldOptions): FieldDefinition {
|
|
419
|
+
return { type: 'phone', source, options: { linked: true, ...options } };
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
423
|
+
// Numeric Fields
|
|
424
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
number(source: string, options?: NumberFieldOptions): FieldDefinition {
|
|
427
|
+
return { type: 'number', source, options: options ?? {} };
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
currency(source: string, options?: CurrencyFieldOptions): FieldDefinition {
|
|
431
|
+
return { type: 'currency', source, options: { currency: 'USD', showSymbol: true, ...options } };
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
percent(source: string, options?: PercentFieldOptions): FieldDefinition {
|
|
435
|
+
return { type: 'percent', source, options: { decimals: 1, ...options } };
|
|
436
|
+
},
|
|
437
|
+
|
|
438
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
439
|
+
// Date Fields
|
|
440
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
date(source: string, options?: DateFieldOptions): FieldDefinition {
|
|
443
|
+
return { type: 'date', source, options: { format: 'medium', ...options } };
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
datetime(source: string, options?: DateFieldOptions): FieldDefinition {
|
|
447
|
+
return { type: 'datetime', source, options: { format: 'medium', showTime: true, ...options } };
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
time(source: string, options?: TimeFieldOptions): FieldDefinition {
|
|
451
|
+
return { type: 'time', source, options: options ?? {} };
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
dateRange(startSource: string, options: DateRangeFieldOptions): FieldDefinition {
|
|
455
|
+
return { type: 'dateRange', source: startSource, options: { separator: ' → ', ...options } };
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
459
|
+
// Boolean Fields
|
|
460
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
461
|
+
|
|
462
|
+
boolean(source: string, options?: BooleanFieldOptions): FieldDefinition {
|
|
463
|
+
return { type: 'boolean', source, options: { trueIcon: '✓', falseIcon: '✗', ...options } };
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
467
|
+
// Media Fields
|
|
468
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
image(source: string, options?: ImageFieldOptions): FieldDefinition {
|
|
471
|
+
return { type: 'image', source, options: options ?? {} };
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
avatar(source: string, options?: AvatarFieldOptions): FieldDefinition {
|
|
475
|
+
return { type: 'avatar', source, options: { size: 40, ...options } };
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
gallery(source: string, options?: GalleryFieldOptions): FieldDefinition {
|
|
479
|
+
return { type: 'gallery', source, options: { maxVisible: 4, thumbnailSize: 60, ...options } };
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
file(source: string, options?: FileFieldOptions): FieldDefinition {
|
|
483
|
+
return { type: 'file', source, options: { showIcon: true, ...options } };
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
487
|
+
// Status/Category Fields
|
|
488
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
badge(source: string, options?: BadgeFieldOptions): FieldDefinition {
|
|
491
|
+
return { type: 'badge', source, options: { variant: 'subtle', ...options } };
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
tags(source: string, options?: TagsFieldOptions): FieldDefinition {
|
|
495
|
+
return { type: 'tags', source, options: { max: 3, ...options } };
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
499
|
+
// Rating Fields
|
|
500
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
rating(source: string, options?: RatingFieldOptions): FieldDefinition {
|
|
503
|
+
return { type: 'rating', source, options: { max: 5, icon: '★', color: '#f59e0b', ...options } };
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
507
|
+
// Commerce Fields
|
|
508
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
price(source: string, options?: PriceFieldOptions): FieldDefinition {
|
|
511
|
+
return { type: 'price', source, options: { currency: 'USD', ...options } };
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
stock(source: string, options?: StockFieldOptions): FieldDefinition {
|
|
515
|
+
return {
|
|
516
|
+
type: 'stock',
|
|
517
|
+
source,
|
|
518
|
+
options: {
|
|
519
|
+
inStockLabel: 'In Stock',
|
|
520
|
+
outOfStockLabel: 'Out of Stock',
|
|
521
|
+
lowStockThreshold: 5,
|
|
522
|
+
lowStockLabel: 'Low Stock',
|
|
523
|
+
...options,
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
quantity(source: string, options?: QuantityFieldOptions): FieldDefinition {
|
|
529
|
+
return { type: 'quantity', source, options: { min: 1, max: 99, step: 1, ...options } };
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
533
|
+
// Reference Fields
|
|
534
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
user(source: string, options?: UserFieldOptions): FieldDefinition {
|
|
537
|
+
return { type: 'user', source, options: options ?? {} };
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
reference(source: string, options?: ReferenceFieldOptions): FieldDefinition {
|
|
541
|
+
return { type: 'reference', source, options: options ?? {} };
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
545
|
+
// Action Fields
|
|
546
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
actions(items: ActionItem[], options?: ActionsFieldOptions): FieldDefinition {
|
|
549
|
+
return { type: 'actions', source: '', options: options ?? {}, actions: items };
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
553
|
+
// Custom Field
|
|
554
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
custom(source: string, render: RenderFunction, options?: BaseFieldOptions): FieldDefinition {
|
|
557
|
+
return { type: 'custom', source, options: options ?? {}, render };
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
562
|
+
// Field Utilities
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Get value from record using dot notation
|
|
567
|
+
*/
|
|
568
|
+
export function getFieldValue(record: Record<string, any>, source: string): any {
|
|
569
|
+
if (!source) return undefined;
|
|
570
|
+
return source.split('.').reduce((obj, key) => obj?.[key], record);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Format label from source (camelCase → Title Case)
|
|
575
|
+
*/
|
|
576
|
+
export function formatFieldLabel(source: string): string {
|
|
577
|
+
const lastPart = source.split('.').pop() ?? source;
|
|
578
|
+
return lastPart
|
|
579
|
+
.replace(/([A-Z])/g, ' $1')
|
|
580
|
+
.replace(/[_-]/g, ' ')
|
|
581
|
+
.replace(/^\w/, c => c.toUpperCase())
|
|
582
|
+
.trim();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Interpolate template string with record values
|
|
587
|
+
* e.g., "/users/{id}" with { id: 123 } → "/users/123"
|
|
588
|
+
*/
|
|
589
|
+
export function interpolateTemplate(template: string, record: Record<string, any>): string {
|
|
590
|
+
return template.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
591
|
+
const value = getFieldValue(record, key);
|
|
592
|
+
return value !== undefined ? String(value) : '';
|
|
593
|
+
});
|
|
594
|
+
}
|