@object-ui/core 3.3.0 → 3.3.2
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/CHANGELOG.md +12 -0
- package/README.md +20 -1
- package/dist/actions/ActionRunner.d.ts +9 -0
- package/dist/actions/ActionRunner.js +41 -4
- package/dist/adapters/ValueDataSource.js +3 -1
- package/dist/registry/Registry.d.ts +47 -0
- package/dist/registry/Registry.js +92 -0
- package/dist/utils/filter-converter.js +25 -5
- package/package.json +32 -8
- package/.turbo/turbo-build.log +0 -4
- package/src/__benchmarks__/core.bench.ts +0 -64
- package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
- package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
- package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
- package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
- package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
- package/src/actions/ActionEngine.ts +0 -268
- package/src/actions/ActionRunner.ts +0 -717
- package/src/actions/TransactionManager.ts +0 -521
- package/src/actions/UndoManager.ts +0 -215
- package/src/actions/__tests__/ActionEngine.test.ts +0 -206
- package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
- package/src/actions/__tests__/ActionRunner.test.ts +0 -711
- package/src/actions/__tests__/TransactionManager.test.ts +0 -447
- package/src/actions/__tests__/UndoManager.test.ts +0 -320
- package/src/actions/index.ts +0 -12
- package/src/adapters/ApiDataSource.ts +0 -376
- package/src/adapters/README.md +0 -180
- package/src/adapters/ValueDataSource.ts +0 -459
- package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
- package/src/adapters/__tests__/ValueDataSource.test.ts +0 -571
- package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
- package/src/adapters/index.ts +0 -15
- package/src/adapters/resolveDataSource.ts +0 -79
- package/src/builder/__tests__/schema-builder.test.ts +0 -235
- package/src/builder/schema-builder.ts +0 -584
- package/src/data-scope/DataScopeManager.ts +0 -269
- package/src/data-scope/ViewDataProvider.ts +0 -282
- package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
- package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
- package/src/data-scope/index.ts +0 -24
- package/src/errors/__tests__/errors.test.ts +0 -292
- package/src/errors/index.ts +0 -269
- package/src/evaluator/ExpressionCache.ts +0 -206
- package/src/evaluator/ExpressionContext.ts +0 -118
- package/src/evaluator/ExpressionEvaluator.ts +0 -315
- package/src/evaluator/FormulaFunctions.ts +0 -398
- package/src/evaluator/SafeExpressionParser.ts +0 -893
- package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
- package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
- package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -558
- package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
- package/src/evaluator/index.ts +0 -13
- package/src/index.ts +0 -38
- package/src/protocols/DndProtocol.ts +0 -168
- package/src/protocols/KeyboardProtocol.ts +0 -181
- package/src/protocols/NotificationProtocol.ts +0 -150
- package/src/protocols/ResponsiveProtocol.ts +0 -210
- package/src/protocols/SharingProtocol.ts +0 -185
- package/src/protocols/index.ts +0 -13
- package/src/query/__tests__/query-ast.test.ts +0 -211
- package/src/query/__tests__/window-functions.test.ts +0 -275
- package/src/query/index.ts +0 -7
- package/src/query/query-ast.ts +0 -341
- package/src/registry/PluginScopeImpl.ts +0 -259
- package/src/registry/PluginSystem.ts +0 -206
- package/src/registry/Registry.ts +0 -219
- package/src/registry/WidgetRegistry.ts +0 -316
- package/src/registry/__tests__/PluginSystem.test.ts +0 -309
- package/src/registry/__tests__/Registry.test.ts +0 -293
- package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
- package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
- package/src/theme/ThemeEngine.ts +0 -530
- package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
- package/src/theme/index.ts +0 -24
- package/src/types/index.ts +0 -21
- package/src/utils/__tests__/debug-collector.test.ts +0 -102
- package/src/utils/__tests__/debug.test.ts +0 -134
- package/src/utils/__tests__/expand-fields.test.ts +0 -120
- package/src/utils/__tests__/extract-records.test.ts +0 -50
- package/src/utils/__tests__/filter-converter.test.ts +0 -118
- package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
- package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
- package/src/utils/debug-collector.ts +0 -100
- package/src/utils/debug.ts +0 -148
- package/src/utils/expand-fields.ts +0 -76
- package/src/utils/extract-records.ts +0 -33
- package/src/utils/filter-converter.ts +0 -133
- package/src/utils/merge-views-into-objects.ts +0 -36
- package/src/utils/normalize-quick-filter.ts +0 -78
- package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
- package/src/validation/__tests__/schema-validator.test.ts +0 -118
- package/src/validation/__tests__/validation-engine.test.ts +0 -102
- package/src/validation/index.ts +0 -10
- package/src/validation/schema-validator.ts +0 -344
- package/src/validation/validation-engine.ts +0 -528
- package/src/validation/validators/index.ts +0 -25
- package/src/validation/validators/object-validation-engine.ts +0 -722
- package/tsconfig.json +0 -15
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -2
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @object-ui/core - Keyboard Protocol Bridge
|
|
11
|
-
*
|
|
12
|
-
* Converts spec-aligned keyboard navigation and focus management
|
|
13
|
-
* schemas into runtime-usable configurations, shortcut parsers,
|
|
14
|
-
* and focus trap settings.
|
|
15
|
-
*
|
|
16
|
-
* @module protocols/KeyboardProtocol
|
|
17
|
-
* @packageDocumentation
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type {
|
|
21
|
-
KeyboardNavigationConfig,
|
|
22
|
-
KeyboardShortcut,
|
|
23
|
-
FocusManagement,
|
|
24
|
-
FocusTrapConfig,
|
|
25
|
-
} from '@object-ui/types';
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Resolved Types
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
/** Fully resolved keyboard navigation configuration. */
|
|
32
|
-
export interface ResolvedKeyboardConfig {
|
|
33
|
-
shortcuts: KeyboardShortcut[];
|
|
34
|
-
focusManagement: ResolvedFocusManagement;
|
|
35
|
-
rovingTabindex: boolean;
|
|
36
|
-
ariaLabel?: string;
|
|
37
|
-
ariaDescribedBy?: string;
|
|
38
|
-
role?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Fully resolved focus management configuration. */
|
|
42
|
-
export interface ResolvedFocusManagement {
|
|
43
|
-
tabOrder: 'auto' | 'manual';
|
|
44
|
-
skipLinks: boolean;
|
|
45
|
-
focusVisible: boolean;
|
|
46
|
-
focusTrap?: ResolvedFocusTrapConfig;
|
|
47
|
-
arrowNavigation: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Fully resolved focus trap configuration. */
|
|
51
|
-
export interface ResolvedFocusTrapConfig {
|
|
52
|
-
enabled: boolean;
|
|
53
|
-
initialFocus?: string;
|
|
54
|
-
returnFocus: boolean;
|
|
55
|
-
escapeDeactivates: boolean;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Parsed keyboard shortcut descriptor. */
|
|
59
|
-
export interface ParsedShortcut {
|
|
60
|
-
key: string;
|
|
61
|
-
ctrlOrMeta: boolean;
|
|
62
|
-
shift: boolean;
|
|
63
|
-
alt: boolean;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Minimal keyboard event shape for matching (no React dependency). */
|
|
67
|
-
export interface KeyboardEventLike {
|
|
68
|
-
key: string;
|
|
69
|
-
ctrlKey?: boolean;
|
|
70
|
-
metaKey?: boolean;
|
|
71
|
-
shiftKey?: boolean;
|
|
72
|
-
altKey?: boolean;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ============================================================================
|
|
76
|
-
// Keyboard Config Resolution
|
|
77
|
-
// ============================================================================
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Resolve a keyboard navigation configuration by applying spec defaults.
|
|
81
|
-
*
|
|
82
|
-
* @param config - KeyboardNavigationConfig from the spec
|
|
83
|
-
* @returns Fully resolved keyboard navigation configuration
|
|
84
|
-
*/
|
|
85
|
-
export function resolveKeyboardConfig(config: KeyboardNavigationConfig): ResolvedKeyboardConfig {
|
|
86
|
-
return {
|
|
87
|
-
shortcuts: config.shortcuts ?? [],
|
|
88
|
-
focusManagement: resolveFocusManagement(config.focusManagement),
|
|
89
|
-
rovingTabindex: config.rovingTabindex ?? false,
|
|
90
|
-
ariaLabel: config.ariaLabel,
|
|
91
|
-
ariaDescribedBy: config.ariaDescribedBy,
|
|
92
|
-
role: config.role,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Resolve focus management configuration with defaults.
|
|
98
|
-
*/
|
|
99
|
-
function resolveFocusManagement(fm?: FocusManagement): ResolvedFocusManagement {
|
|
100
|
-
return {
|
|
101
|
-
tabOrder: fm?.tabOrder ?? 'auto',
|
|
102
|
-
skipLinks: fm?.skipLinks ?? false,
|
|
103
|
-
focusVisible: fm?.focusVisible ?? true,
|
|
104
|
-
focusTrap: fm?.focusTrap ? createFocusTrapConfig(fm.focusTrap) : undefined,
|
|
105
|
-
arrowNavigation: fm?.arrowNavigation ?? false,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ============================================================================
|
|
110
|
-
// Shortcut Parsing
|
|
111
|
-
// ============================================================================
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Parse a shortcut string like "Ctrl+Shift+S" into a structured descriptor.
|
|
115
|
-
* Recognises Ctrl, Meta, Cmd (mapped to ctrlOrMeta), Shift, and Alt modifiers.
|
|
116
|
-
* The last segment is treated as the key.
|
|
117
|
-
*
|
|
118
|
-
* Keys are normalised to lowercase for case-insensitive matching. The Shift
|
|
119
|
-
* modifier is tracked separately, so "Shift+A" and "Shift+a" are equivalent
|
|
120
|
-
* (both represent the same physical key combination).
|
|
121
|
-
*
|
|
122
|
-
* @param shortcut - Shortcut string (e.g. "Ctrl+S", "Alt+Shift+N", "Escape")
|
|
123
|
-
* @returns Parsed shortcut descriptor
|
|
124
|
-
*/
|
|
125
|
-
export function parseShortcutKey(shortcut: string): ParsedShortcut {
|
|
126
|
-
const parts = shortcut.split('+').map(p => p.trim());
|
|
127
|
-
const modifiers = new Set(parts.slice(0, -1).map(m => m.toLowerCase()));
|
|
128
|
-
const key = (parts[parts.length - 1] ?? '').toLowerCase();
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
key,
|
|
132
|
-
ctrlOrMeta: modifiers.has('ctrl') || modifiers.has('meta') || modifiers.has('cmd'),
|
|
133
|
-
shift: modifiers.has('shift'),
|
|
134
|
-
alt: modifiers.has('alt'),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ============================================================================
|
|
139
|
-
// Shortcut Matching
|
|
140
|
-
// ============================================================================
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Test whether a keyboard event matches a shortcut string.
|
|
144
|
-
* Comparison is case-insensitive; the Shift modifier is checked separately.
|
|
145
|
-
*
|
|
146
|
-
* @param event - The keyboard event (or a plain object with key + modifier flags)
|
|
147
|
-
* @param shortcut - Shortcut string (e.g. "Ctrl+S")
|
|
148
|
-
* @returns `true` if the event matches the shortcut
|
|
149
|
-
*/
|
|
150
|
-
export function matchesShortcut(event: KeyboardEventLike, shortcut: string): boolean {
|
|
151
|
-
const parsed = parseShortcutKey(shortcut);
|
|
152
|
-
|
|
153
|
-
if (event.key.toLowerCase() !== parsed.key) return false;
|
|
154
|
-
|
|
155
|
-
const eventCtrlOrMeta = !!(event.ctrlKey || event.metaKey);
|
|
156
|
-
if (parsed.ctrlOrMeta !== eventCtrlOrMeta) return false;
|
|
157
|
-
|
|
158
|
-
if (parsed.shift !== !!(event.shiftKey)) return false;
|
|
159
|
-
if (parsed.alt !== !!(event.altKey)) return false;
|
|
160
|
-
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ============================================================================
|
|
165
|
-
// Focus Trap Configuration
|
|
166
|
-
// ============================================================================
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Create a resolved focus trap configuration from a spec FocusTrapConfig.
|
|
170
|
-
*
|
|
171
|
-
* @param config - FocusTrapConfig from the spec
|
|
172
|
-
* @returns Resolved focus trap configuration with defaults applied
|
|
173
|
-
*/
|
|
174
|
-
export function createFocusTrapConfig(config: FocusTrapConfig): ResolvedFocusTrapConfig {
|
|
175
|
-
return {
|
|
176
|
-
enabled: config.enabled ?? false,
|
|
177
|
-
initialFocus: config.initialFocus,
|
|
178
|
-
returnFocus: config.returnFocus ?? true,
|
|
179
|
-
escapeDeactivates: config.escapeDeactivates ?? true,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @object-ui/core - Notification Protocol Bridge
|
|
11
|
-
*
|
|
12
|
-
* Converts spec-aligned Notification schemas into toast-compatible
|
|
13
|
-
* objects that UI layers can render. Maps severity to variant,
|
|
14
|
-
* position names, and resolves default notification configs.
|
|
15
|
-
*
|
|
16
|
-
* @module protocols/NotificationProtocol
|
|
17
|
-
* @packageDocumentation
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type {
|
|
21
|
-
Notification as SpecNotification,
|
|
22
|
-
NotificationAction,
|
|
23
|
-
NotificationConfig,
|
|
24
|
-
NotificationPosition,
|
|
25
|
-
NotificationSeverity,
|
|
26
|
-
} from '@object-ui/types';
|
|
27
|
-
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Resolved Types
|
|
30
|
-
// ============================================================================
|
|
31
|
-
|
|
32
|
-
/** Fully resolved notification configuration. */
|
|
33
|
-
export interface ResolvedNotificationConfig {
|
|
34
|
-
defaultPosition: NotificationPosition;
|
|
35
|
-
defaultDuration: number;
|
|
36
|
-
maxVisible: number;
|
|
37
|
-
stackDirection: 'up' | 'down';
|
|
38
|
-
pauseOnHover: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Toast-compatible representation of a spec Notification. */
|
|
42
|
-
export interface ToastNotification {
|
|
43
|
-
title?: string;
|
|
44
|
-
description: string;
|
|
45
|
-
variant: string;
|
|
46
|
-
position: string;
|
|
47
|
-
duration: number;
|
|
48
|
-
dismissible: boolean;
|
|
49
|
-
actions: ToastAction[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Toast-compatible action button. */
|
|
53
|
-
export interface ToastAction {
|
|
54
|
-
label: string;
|
|
55
|
-
action: string;
|
|
56
|
-
variant: 'primary' | 'secondary' | 'link';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ============================================================================
|
|
60
|
-
// Severity → Variant Mapping
|
|
61
|
-
// ============================================================================
|
|
62
|
-
|
|
63
|
-
const SEVERITY_TO_VARIANT: Record<string, string> = {
|
|
64
|
-
info: 'default',
|
|
65
|
-
success: 'success',
|
|
66
|
-
warning: 'warning',
|
|
67
|
-
error: 'destructive',
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Map a spec notification severity to a toast variant string.
|
|
72
|
-
*
|
|
73
|
-
* @param severity - Spec severity (info, success, warning, error)
|
|
74
|
-
* @returns Toast variant (default, success, warning, destructive)
|
|
75
|
-
*/
|
|
76
|
-
export function mapSeverityToVariant(severity: string): string {
|
|
77
|
-
return SEVERITY_TO_VARIANT[severity] ?? 'default';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Position Mapping
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
const POSITION_MAP: Record<string, string> = {
|
|
85
|
-
top_left: 'top-left',
|
|
86
|
-
top_center: 'top-center',
|
|
87
|
-
top_right: 'top-right',
|
|
88
|
-
bottom_left: 'bottom-left',
|
|
89
|
-
bottom_center: 'bottom-center',
|
|
90
|
-
bottom_right: 'bottom-right',
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Map a spec notification position (underscore-separated) to a
|
|
95
|
-
* toast position string (hyphen-separated).
|
|
96
|
-
*
|
|
97
|
-
* @param position - Spec position (e.g. "top_right")
|
|
98
|
-
* @returns Toast position (e.g. "top-right")
|
|
99
|
-
*/
|
|
100
|
-
export function mapPosition(position: string): string {
|
|
101
|
-
return POSITION_MAP[position] ?? 'top-right';
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ============================================================================
|
|
105
|
-
// Notification Config Resolution
|
|
106
|
-
// ============================================================================
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Resolve a notification configuration by applying spec defaults.
|
|
110
|
-
*
|
|
111
|
-
* @param config - NotificationConfig from the spec
|
|
112
|
-
* @returns Fully resolved notification configuration
|
|
113
|
-
*/
|
|
114
|
-
export function resolveNotificationConfig(config: NotificationConfig): ResolvedNotificationConfig {
|
|
115
|
-
return {
|
|
116
|
-
defaultPosition: config.defaultPosition ?? 'top_right',
|
|
117
|
-
defaultDuration: config.defaultDuration ?? 5000,
|
|
118
|
-
maxVisible: config.maxVisible ?? 5,
|
|
119
|
-
stackDirection: config.stackDirection ?? 'down',
|
|
120
|
-
pauseOnHover: config.pauseOnHover ?? true,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ============================================================================
|
|
125
|
-
// Spec Notification → Toast
|
|
126
|
-
// ============================================================================
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Convert a spec Notification to a toast-compatible object.
|
|
130
|
-
*
|
|
131
|
-
* @param notification - Spec Notification
|
|
132
|
-
* @returns Toast-compatible notification with title, description, variant, etc.
|
|
133
|
-
*/
|
|
134
|
-
export function specNotificationToToast(notification: SpecNotification): ToastNotification {
|
|
135
|
-
const actions: ToastAction[] = (notification.actions ?? []).map((a: NotificationAction) => ({
|
|
136
|
-
label: a.label,
|
|
137
|
-
action: a.action,
|
|
138
|
-
variant: a.variant ?? 'primary',
|
|
139
|
-
}));
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
title: notification.title,
|
|
143
|
-
description: notification.message ?? '',
|
|
144
|
-
variant: mapSeverityToVariant(notification.severity ?? 'info'),
|
|
145
|
-
position: mapPosition(notification.position ?? 'top_right'),
|
|
146
|
-
duration: notification.duration ?? 5000,
|
|
147
|
-
dismissible: notification.dismissible ?? true,
|
|
148
|
-
actions,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @object-ui/core - Responsive Protocol Bridge
|
|
11
|
-
*
|
|
12
|
-
* Converts spec-aligned ResponsiveConfig schemas into Tailwind CSS
|
|
13
|
-
* utility classes for visibility, grid columns, and ordering across
|
|
14
|
-
* breakpoints. Also provides runtime width-based visibility checks.
|
|
15
|
-
*
|
|
16
|
-
* @module protocols/ResponsiveProtocol
|
|
17
|
-
* @packageDocumentation
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type { SpecResponsiveConfig } from '@object-ui/types';
|
|
21
|
-
|
|
22
|
-
// ============================================================================
|
|
23
|
-
// Breakpoint Definitions
|
|
24
|
-
// ============================================================================
|
|
25
|
-
|
|
26
|
-
/** Breakpoint name type matching Tailwind defaults. */
|
|
27
|
-
export type BreakpointKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
28
|
-
|
|
29
|
-
/** Breakpoint minimum pixel widths aligned with Tailwind CSS defaults. */
|
|
30
|
-
export const BREAKPOINT_VALUES: Record<BreakpointKey, number> = {
|
|
31
|
-
xs: 0,
|
|
32
|
-
sm: 640,
|
|
33
|
-
md: 768,
|
|
34
|
-
lg: 1024,
|
|
35
|
-
xl: 1280,
|
|
36
|
-
'2xl': 1536,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/** Ordered breakpoint keys from smallest to largest. */
|
|
40
|
-
const BREAKPOINT_ORDER: BreakpointKey[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
41
|
-
|
|
42
|
-
// ============================================================================
|
|
43
|
-
// Resolved Types
|
|
44
|
-
// ============================================================================
|
|
45
|
-
|
|
46
|
-
/** Fully resolved responsive configuration. */
|
|
47
|
-
export interface ResolvedResponsiveConfig {
|
|
48
|
-
breakpoint?: BreakpointKey;
|
|
49
|
-
hiddenOn: BreakpointKey[];
|
|
50
|
-
columns: Partial<Record<BreakpointKey, number>>;
|
|
51
|
-
order: Partial<Record<BreakpointKey, number>>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Config Resolution
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Resolve a responsive configuration by applying defaults.
|
|
60
|
-
*
|
|
61
|
-
* @param config - SpecResponsiveConfig from the spec
|
|
62
|
-
* @returns Fully resolved responsive configuration
|
|
63
|
-
*/
|
|
64
|
-
export function resolveResponsiveConfig(config: SpecResponsiveConfig): ResolvedResponsiveConfig {
|
|
65
|
-
return {
|
|
66
|
-
breakpoint: config.breakpoint as BreakpointKey | undefined,
|
|
67
|
-
hiddenOn: (config.hiddenOn ?? []) as BreakpointKey[],
|
|
68
|
-
columns: (config.columns ?? {}) as Partial<Record<BreakpointKey, number>>,
|
|
69
|
-
order: (config.order ?? {}) as Partial<Record<BreakpointKey, number>>,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// Visibility Classes
|
|
75
|
-
// ============================================================================
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Generate Tailwind CSS classes for responsive visibility.
|
|
79
|
-
*
|
|
80
|
-
* If `breakpoint` is set, the element is hidden below that breakpoint
|
|
81
|
-
* (e.g. breakpoint "md" → `['hidden', 'md:block']`).
|
|
82
|
-
*
|
|
83
|
-
* If `hiddenOn` contains breakpoints, the element is hidden at those
|
|
84
|
-
* specific sizes (e.g. hiddenOn: ["sm", "lg"] → `['sm:hidden', 'md:block', 'lg:hidden', 'xl:block']`).
|
|
85
|
-
*
|
|
86
|
-
* @param config - SpecResponsiveConfig from the spec
|
|
87
|
-
* @returns Array of Tailwind CSS class strings
|
|
88
|
-
*/
|
|
89
|
-
export function getVisibilityClasses(config: SpecResponsiveConfig): string[] {
|
|
90
|
-
const classes: string[] = [];
|
|
91
|
-
|
|
92
|
-
// Minimum breakpoint visibility
|
|
93
|
-
if (config.breakpoint) {
|
|
94
|
-
const bp = config.breakpoint as BreakpointKey;
|
|
95
|
-
if (bp !== 'xs') {
|
|
96
|
-
classes.push('hidden');
|
|
97
|
-
classes.push(`${bp}:block`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Per-breakpoint hidden overrides
|
|
102
|
-
const hiddenOn = (config.hiddenOn ?? []) as BreakpointKey[];
|
|
103
|
-
if (hiddenOn.length > 0) {
|
|
104
|
-
for (let i = 0; i < BREAKPOINT_ORDER.length; i++) {
|
|
105
|
-
const bp = BREAKPOINT_ORDER[i];
|
|
106
|
-
const isHidden = hiddenOn.includes(bp);
|
|
107
|
-
const prevHidden = i > 0 ? hiddenOn.includes(BREAKPOINT_ORDER[i - 1]) : false;
|
|
108
|
-
|
|
109
|
-
if (isHidden && !prevHidden) {
|
|
110
|
-
classes.push(bp === 'xs' ? 'hidden' : `${bp}:hidden`);
|
|
111
|
-
} else if (!isHidden && prevHidden) {
|
|
112
|
-
classes.push(bp === 'xs' ? 'block' : `${bp}:block`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return classes;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ============================================================================
|
|
121
|
-
// Column Classes
|
|
122
|
-
// ============================================================================
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Generate Tailwind grid-cols classes for responsive column layouts.
|
|
126
|
-
*
|
|
127
|
-
* @param config - SpecResponsiveConfig from the spec
|
|
128
|
-
* @returns Array of Tailwind CSS grid column class strings
|
|
129
|
-
*/
|
|
130
|
-
export function getColumnClasses(config: SpecResponsiveConfig): string[] {
|
|
131
|
-
const classes: string[] = [];
|
|
132
|
-
const columns = (config.columns ?? {}) as Partial<Record<BreakpointKey, number>>;
|
|
133
|
-
|
|
134
|
-
for (const bp of BREAKPOINT_ORDER) {
|
|
135
|
-
const cols = columns[bp];
|
|
136
|
-
if (cols == null) continue;
|
|
137
|
-
const prefix = bp === 'xs' ? '' : `${bp}:`;
|
|
138
|
-
classes.push(`${prefix}grid-cols-${cols}`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return classes;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ============================================================================
|
|
145
|
-
// Order Classes
|
|
146
|
-
// ============================================================================
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Generate Tailwind order utility classes for responsive ordering.
|
|
150
|
-
*
|
|
151
|
-
* @param config - SpecResponsiveConfig from the spec
|
|
152
|
-
* @returns Array of Tailwind CSS order class strings
|
|
153
|
-
*/
|
|
154
|
-
export function getOrderClasses(config: SpecResponsiveConfig): string[] {
|
|
155
|
-
const classes: string[] = [];
|
|
156
|
-
const order = (config.order ?? {}) as Partial<Record<BreakpointKey, number>>;
|
|
157
|
-
|
|
158
|
-
for (const bp of BREAKPOINT_ORDER) {
|
|
159
|
-
const ord = order[bp];
|
|
160
|
-
if (ord == null) continue;
|
|
161
|
-
const prefix = bp === 'xs' ? '' : `${bp}:`;
|
|
162
|
-
classes.push(`${prefix}order-${ord}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return classes;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ============================================================================
|
|
169
|
-
// Runtime Width Check
|
|
170
|
-
// ============================================================================
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Determine whether a component should be hidden at a given viewport width.
|
|
174
|
-
*
|
|
175
|
-
* Checks both the minimum `breakpoint` threshold and the `hiddenOn` list.
|
|
176
|
-
*
|
|
177
|
-
* @param config - SpecResponsiveConfig from the spec
|
|
178
|
-
* @param width - Current viewport width in pixels
|
|
179
|
-
* @returns `true` if the component should be hidden at the given width
|
|
180
|
-
*/
|
|
181
|
-
export function shouldHideAtBreakpoint(config: SpecResponsiveConfig, width: number): boolean {
|
|
182
|
-
// Check minimum breakpoint
|
|
183
|
-
if (config.breakpoint) {
|
|
184
|
-
const minWidth = BREAKPOINT_VALUES[config.breakpoint as BreakpointKey];
|
|
185
|
-
if (minWidth !== undefined && width < minWidth) {
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Check hiddenOn list
|
|
191
|
-
const hiddenOn = (config.hiddenOn ?? []) as BreakpointKey[];
|
|
192
|
-
if (hiddenOn.length > 0) {
|
|
193
|
-
const currentBp = getCurrentBreakpoint(width);
|
|
194
|
-
return hiddenOn.includes(currentBp);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Determine the current breakpoint name for a given width.
|
|
202
|
-
*/
|
|
203
|
-
function getCurrentBreakpoint(width: number): BreakpointKey {
|
|
204
|
-
for (let i = BREAKPOINT_ORDER.length - 1; i >= 0; i--) {
|
|
205
|
-
if (width >= BREAKPOINT_VALUES[BREAKPOINT_ORDER[i]]) {
|
|
206
|
-
return BREAKPOINT_ORDER[i];
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return 'xs';
|
|
210
|
-
}
|