-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathui-action.ts
More file actions
495 lines (414 loc) · 13.7 KB
/
Copy pathui-action.ts
File metadata and controls
495 lines (414 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @object-ui/types - UI Action Schema
*
* ObjectStack Spec v2.0.1 compliant action schema with enhanced capabilities:
* - Location-based action placement
* - Parameter collection
* - Conditional visibility and enablement
* - Rich feedback mechanisms
*
* @module ui-action
* @packageDocumentation
*/
import { z } from 'zod';
import type { ActionType as SpecActionType } from '@objectstack/spec/ui';
// ============================================================================
// Spec-Canonical Action Sub-types — imported from @objectstack/spec/ui
// ============================================================================
/**
* Action placement locations.
*
* Single source of truth lives in `@objectstack/spec/ui` as
* `ACTION_LOCATIONS` + `ActionLocationSchema` + `ActionLocation`. Re-export
* here so existing `@object-ui/types` consumers keep working without coupling
* them to a duplicated, drift-prone enum. To add a new location, edit
* `packages/spec/src/ui/action.zod.ts` — every layer (spec, core, types,
* Studio designer dropdowns) picks up the new value automatically.
*/
export type ActionLocation =
| 'list_toolbar'
| 'list_item'
| 'record_header'
| 'record_more'
| 'record_related'
| 'record_section'
| 'global_nav';
export const ACTION_LOCATIONS = [
'list_toolbar',
'list_item',
'record_header',
'record_more',
'record_related',
'record_section',
'global_nav',
] as const satisfies readonly ActionLocation[];
export const ActionLocationSchema = z.enum(ACTION_LOCATIONS);
/**
* Visual component type for actions
* Canonical definition from @objectstack/spec/ui ActionSchema.component.
*/
export type ActionComponent =
| 'action:button' // Standard button
| 'action:icon' // Icon-only button
| 'action:menu' // Menu item
| 'action:group'; // Action group/dropdown
/**
* Action execution type — derived from `@objectstack/spec`'s `ActionType` enum
* (issue #2231/#2901; formerly a hand-written union).
*
* `script` | `url` | `modal` | `flow` | `api` | `form`
*
* The previous union claimed to be the "canonical definition from
* @objectstack/spec" and was missing `form` — so a host app typing against
* `@object-ui/types` got a type error on `type: 'form'` even though
* `ActionRunner.executeForm` implements it. Derived now, so the claim is
* enforced by the compiler rather than asserted in a comment.
*/
export type ActionType = z.infer<typeof SpecActionType>;
/**
* Field type for action parameters
* Subset of field types commonly used in action parameter collection UIs.
* Aligned with the field types available in @objectstack/spec ActionParamSchema.
*/
export type ActionParamFieldType =
| 'text'
| 'textarea'
| 'number'
| 'boolean'
| 'date'
| 'datetime'
| 'time'
| 'select'
| 'email'
| 'phone'
| 'url'
| 'password'
| 'file'
| 'color'
| 'slider'
| 'rating';
/**
* Action parameter definition (ObjectStack Spec v2.0.1)
*/
export interface ActionParam {
/** Parameter name (snake_case) */
name: string;
/** Display label */
label: string;
/** Field type for input */
type: ActionParamFieldType;
/** Whether parameter is required */
required?: boolean;
/** Options for select/picklist types */
options?: Array<{ label: string; value: string }>;
/** Default value */
defaultValue?: unknown;
/** Help text */
helpText?: string;
/** Placeholder text */
placeholder?: string;
/** Validation expression */
validation?: string;
}
/**
* Enhanced Action Schema (ObjectStack Spec v2.0.1)
*
* This is the primary action schema that should be used for all new implementations.
* The legacy ActionSchema in crud.ts is maintained for backward compatibility.
*/
export interface ActionSchema {
/** Unique action identifier (snake_case) */
name: string;
/** Display label */
label: string;
/** Optional icon (Lucide icon name) */
icon?: string;
// === Placement ===
/** Where to show this action (defaults to ['record_header']) */
locations?: ActionLocation[];
/** Visual component type (defaults to 'action:button') */
component?: ActionComponent;
/**
* Sort order within a location group (lower = higher / more prominent;
* defaults to 0). The action:bar stable-sorts actions by `order` before the
* inline/overflow split, so in `record_header` a lower `order` promotes an
* action into the primary-button slot and a higher `order` pushes it toward
* the "More" (⋯) overflow menu. Mirrors `Action.order` in `@objectstack/spec`.
*/
order?: number;
// === Behavior ===
/** Action execution type */
type: ActionType;
/** Target for the action (URL, script name, etc.) */
target?: string;
/**
* For `type: 'url'`: where to open `target`.
* - `'new-tab'` — open `target` in a new browser tab/window.
* - `'self'` — navigate the current page (in-app router when available).
*
* When omitted, the legacy heuristic applies: absolute/external URLs
* (`http(s)://…`) open in a new tab, relative URLs navigate in place.
*
* This is a **static execution option**, NOT user input — keep it out of
* {@link params}, which is exclusively for collecting input before
* execution. Putting `newTab` inside `params` is rejected at build (an
* object where an `ActionParam[]` is expected) and, as an array entry,
* is mis-rendered as a checkbox in the param-collection dialog.
*
* Note: this is distinct from the runner-internal `opensInNewTab` /
* `newTabUrl` pair, which pre-opens `about:blank` synchronously for async
* handlers that resolve a redirect URL after a fetch (SSO flows). For a
* static `target`, prefer `openIn`.
*/
openIn?: 'self' | 'new-tab';
/** Script to execute (for type: 'script') */
execute?: string;
/** API endpoint (for type: 'api') */
endpoint?: string;
/** HTTP method (for type: 'api') */
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
// === Parameters ===
/**
* Input parameters to collect from the user before execution (renders a
* dialog). This field is **exclusively** for user-input collection — it is
* always an `ActionParam[]`. Do NOT use it to carry static execution
* options (e.g. new-tab behavior — use {@link openIn}); a non-array value
* fails build validation, and an array entry like
* `{ name: 'newTab', type: 'checkbox' }` is mis-rendered as a user-facing
* checkbox instead of being treated as an instruction.
*/
params?: ActionParam[];
// === Feedback ===
/** Confirmation text to show before execution */
confirmText?: string;
/** Success message to show after execution */
successMessage?: string;
/** Error message to show on failure */
errorMessage?: string;
/** Whether to refresh data after execution */
refreshAfter?: boolean;
/** Toast notification configuration */
toast?: {
/** Show toast on success */
showOnSuccess?: boolean;
/** Show toast on error */
showOnError?: boolean;
/** Toast duration in milliseconds */
duration?: number;
};
// === Conditional ===
/** Expression controlling visibility (e.g., "status === 'draft'") */
visible?: string;
/** Expression controlling enabled state (e.g., "hasPermission('edit')") */
enabled?: string;
// === Styling ===
/** Button variant */
variant?: 'default' | 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost';
/** Button size */
size?: 'sm' | 'md' | 'lg';
/** Custom CSS class */
className?: string;
// === Metadata ===
/** Action description */
description?: string;
/** Permission required to execute */
permission?: string;
/** Tags for categorization */
tags?: string[];
/**
* UI-local escape hatch: synchronous/async callback invoked directly by
* UI action renderers (e.g., `action:menu`) instead of routing through
* {@link ActionEngine}. Intended for chrome-level concerns such as
* toggling inline-edit mode, opening a native Share sheet, or copying the
* URL to the clipboard — UI side-effects that are not part of the domain
* action protocol and therefore need not be serialized over the wire.
*
* When present, `onClick` takes precedence over `type` / `target` /
* `execute`. Prefer {@link ActionEngine}-routed actions for anything that
* could originate from server-driven metadata.
*/
onClick?: () => void | Promise<void>;
}
/**
* Action group for organizing related actions
*/
export interface ActionGroup {
/** Group name */
name: string;
/** Display label */
label: string;
/** Optional icon */
icon?: string;
/** Actions in this group */
actions: ActionSchema[];
/** Group visibility condition */
visible?: string;
/** Display as dropdown or inline */
display?: 'dropdown' | 'inline';
}
/**
* Action execution context
*/
export interface ActionContext {
/** Current record data */
record?: Record<string, any>;
/** Selected records (for list actions) */
selectedRecords?: Record<string, any>[];
/** Live page-variable snapshot (ADR-0049) published by PageVariableActionBridge —
* lets a submit action read page-local form state via `{{page.<var>}}` tokens. */
pageVariables?: Record<string, any>;
/** Current user */
user?: Record<string, any>;
/** Additional context data */
[key: string]: any;
}
/**
* Action execution result
*/
export interface ActionResult {
/** Whether action succeeded */
success: boolean;
/** Result data */
data?: any;
/** Error message if failed */
error?: string;
/** Whether to refresh data */
refresh?: boolean;
/** Whether to close dialog/modal */
close?: boolean;
}
/**
* Action executor function type
*/
export type ActionExecutor = (
action: ActionSchema,
context: ActionContext,
params?: Record<string, any>
) => Promise<ActionResult>;
// ============================================================================
// Batch Operations (Q2 2026 - Spec v2.0.1 Enhancement)
// ============================================================================
/** Batch operation configuration */
export interface BatchOperationConfig {
/** Operation name */
name: string;
/** Display label */
label: string;
/** Target action to execute on each record */
action: string;
/** Whether to run in parallel */
parallel?: boolean;
/** Maximum concurrent operations */
concurrency?: number;
/** Whether to continue on error */
continueOnError?: boolean;
/** Progress callback expression */
onProgress?: string;
/** Completion callback expression */
onComplete?: string;
}
/** Batch operation result */
export interface BatchOperationResult {
/** Total items processed */
total: number;
/** Successfully processed count */
succeeded: number;
/** Failed count */
failed: number;
/** Individual results */
results: Array<{
recordId: string;
success: boolean;
error?: string;
}>;
}
// ============================================================================
// Transaction Support (Q2 2026 - Spec v2.0.1 Enhancement)
// ============================================================================
/** Transaction isolation level */
export type TransactionIsolationLevel = 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable';
/** Transaction configuration */
export interface TransactionConfig {
/** Transaction name for identification */
name?: string;
/** Isolation level */
isolation?: TransactionIsolationLevel;
/** Timeout in milliseconds */
timeout?: number;
/** Actions to execute within the transaction */
actions: ActionSchema[];
/** Rollback action on failure */
rollbackAction?: string;
/** Whether to auto-retry on conflict */
retryOnConflict?: boolean;
/** Maximum retry attempts */
maxRetries?: number;
}
/** Transaction result */
export interface TransactionResult {
/** Whether all actions succeeded */
success: boolean;
/** Transaction ID */
transactionId: string;
/** Individual action results */
actionResults: ActionResult[];
/** Error if transaction failed */
error?: string;
/** Whether the transaction was rolled back */
rolledBack?: boolean;
}
// ============================================================================
// Undo/Redo Support (Q2 2026 - Spec v2.0.1 Enhancement)
// ============================================================================
/** Undo/redo operation entry */
export interface UndoRedoEntry {
/** Entry identifier */
id: string;
/** Action that was performed */
action: string;
/** Description of the action */
description: string;
/** Timestamp */
timestamp: string;
/** Data before the action (for undo) */
previousState: Record<string, unknown>;
/** Data after the action (for redo) */
nextState: Record<string, unknown>;
/** Target object */
object?: string;
/** Target record ID */
recordId?: string;
}
/** Undo/redo configuration */
export interface UndoRedoConfig {
/** Enable undo/redo */
enabled: boolean;
/** Maximum history size */
maxHistorySize?: number;
/** Actions that support undo */
undoableActions?: string[];
/** Whether to group rapid changes */
groupChanges?: boolean;
/** Group timeout in milliseconds */
groupTimeout?: number;
}
/** Undo/redo state */
export interface UndoRedoState {
/** Whether undo is available */
canUndo: boolean;
/** Whether redo is available */
canRedo: boolean;
/** Undo stack */
undoStack: UndoRedoEntry[];
/** Redo stack */
redoStack: UndoRedoEntry[];
/** Current position in history */
currentIndex: number;
}