@object-ui/react 2.0.0 → 3.0.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/SchemaRenderer.d.ts +20 -1
- package/dist/SchemaRenderer.js +76 -13
- package/dist/context/DndContext.d.ts +135 -0
- package/dist/context/DndContext.js +120 -0
- package/dist/context/NotificationContext.d.ts +113 -0
- package/dist/context/NotificationContext.js +130 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/hooks/index.d.ts +17 -0
- package/dist/hooks/index.js +17 -0
- package/dist/hooks/useAnimation.d.ts +46 -0
- package/dist/hooks/useAnimation.js +59 -0
- package/dist/hooks/useClientNotifications.d.ts +53 -0
- package/dist/hooks/useClientNotifications.js +190 -0
- package/dist/hooks/useColumnSummary.d.ts +38 -0
- package/dist/hooks/useColumnSummary.js +54 -0
- package/dist/hooks/useCrudShortcuts.d.ts +39 -0
- package/dist/hooks/useCrudShortcuts.js +96 -0
- package/dist/hooks/useDensityMode.d.ts +48 -0
- package/dist/hooks/useDensityMode.js +60 -0
- package/dist/hooks/useETagCache.d.ts +75 -0
- package/dist/hooks/useETagCache.js +306 -0
- package/dist/hooks/useFocusManagement.d.ts +89 -0
- package/dist/hooks/useFocusManagement.js +188 -0
- package/dist/hooks/useFocusTrap.d.ts +41 -0
- package/dist/hooks/useFocusTrap.js +103 -0
- package/dist/hooks/useKeyboardShortcuts.d.ts +49 -0
- package/dist/hooks/useKeyboardShortcuts.js +86 -0
- package/dist/hooks/useOffline.d.ts +96 -0
- package/dist/hooks/useOffline.js +185 -0
- package/dist/hooks/usePageTransition.d.ts +60 -0
- package/dist/hooks/usePageTransition.js +111 -0
- package/dist/hooks/usePerformance.d.ts +87 -0
- package/dist/hooks/usePerformance.js +165 -0
- package/dist/hooks/usePerformanceBudget.d.ts +78 -0
- package/dist/hooks/usePerformanceBudget.js +209 -0
- package/dist/hooks/useReducedMotion.d.ts +28 -0
- package/dist/hooks/useReducedMotion.js +48 -0
- package/dist/hooks/useSchemaPersistence.d.ts +87 -0
- package/dist/hooks/useSchemaPersistence.js +173 -0
- package/dist/hooks/useViewSharing.d.ts +57 -0
- package/dist/hooks/useViewSharing.js +74 -0
- package/dist/hooks/useViewTransition.d.ts +54 -0
- package/dist/hooks/useViewTransition.js +83 -0
- package/package.json +7 -7
- package/src/SchemaRenderer.tsx +122 -13
- package/src/__tests__/SchemaRenderer.aria.test.tsx +118 -0
- package/src/__tests__/SchemaRenderer.errorBoundary.test.tsx +114 -0
- package/src/__tests__/SchemaRendererProvider.smoke.test.tsx +132 -0
- package/src/__tests__/useOffline.test.ts +198 -0
- package/src/__tests__/usePageTransition.test.ts +214 -0
- package/src/__tests__/usePerformance.test.ts +137 -0
- package/src/__tests__/view-switching.test.tsx +456 -0
- package/src/context/DndContext.tsx +260 -0
- package/src/context/NotificationContext.tsx +280 -0
- package/src/context/__tests__/DndContext.test.tsx +150 -0
- package/src/context/__tests__/NotificationContext.test.tsx +176 -0
- package/src/context/index.ts +2 -0
- package/src/hooks/__tests__/useAnimation.test.ts +82 -0
- package/src/hooks/__tests__/useColumnSummary.test.ts +93 -0
- package/src/hooks/__tests__/useDensityMode.test.ts +81 -0
- package/src/hooks/__tests__/useETagCache.test.ts +153 -0
- package/src/hooks/__tests__/useFocusTrap.test.ts +47 -0
- package/src/hooks/__tests__/useKeyboardShortcuts.test.ts +144 -0
- package/src/hooks/__tests__/usePerformanceBudget.test.ts +157 -0
- package/src/hooks/__tests__/useSchemaPersistence.test.ts +220 -0
- package/src/hooks/__tests__/useViewSharing.test.ts +121 -0
- package/src/hooks/__tests__/useViewTransition.test.ts +61 -0
- package/src/hooks/index.ts +17 -1
- package/src/hooks/useAnimation.ts +117 -0
- package/src/hooks/useClientNotifications.ts +251 -0
- package/src/hooks/useColumnSummary.ts +87 -0
- package/src/hooks/useCrudShortcuts.ts +126 -0
- package/src/hooks/useDensityMode.ts +102 -0
- package/src/hooks/useETagCache.ts +404 -0
- package/src/hooks/useFocusManagement.ts +248 -0
- package/src/hooks/useFocusTrap.ts +138 -0
- package/src/hooks/useKeyboardShortcuts.ts +119 -0
- package/src/hooks/useOffline.ts +301 -0
- package/src/hooks/usePageTransition.ts +187 -0
- package/src/hooks/usePerformance.ts +246 -0
- package/src/hooks/usePerformanceBudget.ts +313 -0
- package/src/hooks/useReducedMotion.ts +53 -0
- package/src/hooks/useSchemaPersistence.ts +222 -0
- package/src/hooks/useViewSharing.ts +140 -0
- package/src/hooks/useViewTransition.ts +144 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
10
|
+
|
|
11
|
+
/** View sharing visibility aligned with ViewSharingSchema */
|
|
12
|
+
export type ViewVisibility = 'private' | 'team' | 'organization' | 'public';
|
|
13
|
+
|
|
14
|
+
/** A saved view configuration */
|
|
15
|
+
export interface SavedView {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
/** The view configuration (filters, sort, columns, etc.) */
|
|
19
|
+
config: Record<string, unknown>;
|
|
20
|
+
/** Who can see this view */
|
|
21
|
+
visibility: ViewVisibility;
|
|
22
|
+
/** Who created this view */
|
|
23
|
+
createdBy?: string;
|
|
24
|
+
/** Whether this is the default view */
|
|
25
|
+
isDefault?: boolean;
|
|
26
|
+
/** When the view was last modified */
|
|
27
|
+
updatedAt: Date;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ViewSharingResult {
|
|
31
|
+
/** All available views */
|
|
32
|
+
views: SavedView[];
|
|
33
|
+
/** Currently active view */
|
|
34
|
+
activeView: SavedView | null;
|
|
35
|
+
/** Save a new view or update existing */
|
|
36
|
+
saveView: (view: Omit<SavedView, 'id' | 'updatedAt'>) => string;
|
|
37
|
+
/** Set the active view */
|
|
38
|
+
setActiveView: (viewId: string) => void;
|
|
39
|
+
/** Delete a view */
|
|
40
|
+
deleteView: (viewId: string) => void;
|
|
41
|
+
/** Duplicate a view */
|
|
42
|
+
duplicateView: (viewId: string, newName: string) => string;
|
|
43
|
+
/** Update view visibility */
|
|
44
|
+
setVisibility: (viewId: string, visibility: ViewVisibility) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let viewCounter = 0;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook for managing shared/personal view configurations.
|
|
51
|
+
* Implements ViewSharingSchema from @objectstack/spec v2.0.7.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const viewSharing = useViewSharing(initialViews);
|
|
56
|
+
*
|
|
57
|
+
* // Save current filters/sort as a named view
|
|
58
|
+
* viewSharing.saveView({
|
|
59
|
+
* name: 'My Open Tasks',
|
|
60
|
+
* config: { filters: currentFilters, sort: currentSort },
|
|
61
|
+
* visibility: 'private',
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function useViewSharing(initialViews: SavedView[] = []): ViewSharingResult {
|
|
66
|
+
const [views, setViews] = useState<SavedView[]>(initialViews);
|
|
67
|
+
const [activeViewId, setActiveViewId] = useState<string | null>(
|
|
68
|
+
initialViews.find((v) => v.isDefault)?.id ?? initialViews[0]?.id ?? null
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const activeView = useMemo(
|
|
72
|
+
() => views.find((v) => v.id === activeViewId) ?? null,
|
|
73
|
+
[views, activeViewId]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const saveView = useCallback(
|
|
77
|
+
(input: Omit<SavedView, 'id' | 'updatedAt'>): string => {
|
|
78
|
+
const id = `view-${++viewCounter}`;
|
|
79
|
+
const view: SavedView = {
|
|
80
|
+
...input,
|
|
81
|
+
id,
|
|
82
|
+
updatedAt: new Date(),
|
|
83
|
+
};
|
|
84
|
+
setViews((prev) => [...prev, view]);
|
|
85
|
+
return id;
|
|
86
|
+
},
|
|
87
|
+
[]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const setActiveView = useCallback((viewId: string) => {
|
|
91
|
+
setActiveViewId(viewId);
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const deleteView = useCallback((viewId: string) => {
|
|
95
|
+
setViews((prev) => prev.filter((v) => v.id !== viewId));
|
|
96
|
+
setActiveViewId((current) => (current === viewId ? null : current));
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
const duplicateView = useCallback(
|
|
100
|
+
(viewId: string, newName: string): string => {
|
|
101
|
+
const original = views.find((v) => v.id === viewId);
|
|
102
|
+
if (!original) return '';
|
|
103
|
+
const id = `view-${++viewCounter}`;
|
|
104
|
+
const duplicate: SavedView = {
|
|
105
|
+
...original,
|
|
106
|
+
id,
|
|
107
|
+
name: newName,
|
|
108
|
+
isDefault: false,
|
|
109
|
+
updatedAt: new Date(),
|
|
110
|
+
};
|
|
111
|
+
setViews((prev) => [...prev, duplicate]);
|
|
112
|
+
return id;
|
|
113
|
+
},
|
|
114
|
+
[views]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const setVisibility = useCallback(
|
|
118
|
+
(viewId: string, visibility: ViewVisibility) => {
|
|
119
|
+
setViews((prev) =>
|
|
120
|
+
prev.map((v) =>
|
|
121
|
+
v.id === viewId ? { ...v, visibility, updatedAt: new Date() } : v
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
[]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return useMemo(
|
|
129
|
+
() => ({
|
|
130
|
+
views,
|
|
131
|
+
activeView,
|
|
132
|
+
saveView,
|
|
133
|
+
setActiveView,
|
|
134
|
+
deleteView,
|
|
135
|
+
duplicateView,
|
|
136
|
+
setVisibility,
|
|
137
|
+
}),
|
|
138
|
+
[views, activeView, saveView, setActiveView, deleteView, duplicateView, setVisibility]
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
10
|
+
import { useReducedMotion } from './useReducedMotion';
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Types
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export interface ViewTransitionConfig {
|
|
17
|
+
/** Enable view transitions (default: true) */
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
/** Fallback animation class when View Transitions API is unsupported */
|
|
20
|
+
fallbackClass?: string;
|
|
21
|
+
/** Transition duration in ms (default: 300) */
|
|
22
|
+
duration?: number;
|
|
23
|
+
/** Called before transition starts */
|
|
24
|
+
onTransitionStart?: () => void;
|
|
25
|
+
/** Called after transition completes */
|
|
26
|
+
onTransitionEnd?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ViewTransitionResult {
|
|
30
|
+
/** Whether the View Transitions API is supported */
|
|
31
|
+
isSupported: boolean;
|
|
32
|
+
/** Whether a transition is currently in progress */
|
|
33
|
+
isTransitioning: boolean;
|
|
34
|
+
/** Execute a callback with a view transition */
|
|
35
|
+
startTransition: (callback: () => void | Promise<void>) => Promise<void>;
|
|
36
|
+
/** CSS class to apply during transitions */
|
|
37
|
+
transitionClass: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Helpers
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
const DEFAULT_DURATION = 300;
|
|
45
|
+
const DEFAULT_FALLBACK_CLASS = 'oui-view-transition-fallback';
|
|
46
|
+
|
|
47
|
+
/** Minimal type for the native ViewTransition object returned by the API. */
|
|
48
|
+
interface ViewTransition {
|
|
49
|
+
finished: Promise<void>;
|
|
50
|
+
ready: Promise<void>;
|
|
51
|
+
updateCallbackDone: Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function supportsViewTransitions(): boolean {
|
|
55
|
+
return (
|
|
56
|
+
typeof document !== 'undefined' &&
|
|
57
|
+
'startViewTransition' in document
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// useViewTransition hook
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Hook for integrating with the browser View Transitions API.
|
|
67
|
+
*
|
|
68
|
+
* Uses the native `document.startViewTransition()` when available and
|
|
69
|
+
* falls back to a temporary CSS class for animation when unsupported.
|
|
70
|
+
* Respects `prefers-reduced-motion`.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* function ViewSwitcher({ onViewChange }: { onViewChange: (v: string) => void }) {
|
|
75
|
+
* const { startTransition, transitionClass, isTransitioning } = useViewTransition();
|
|
76
|
+
*
|
|
77
|
+
* const handleChange = (view: string) => {
|
|
78
|
+
* startTransition(() => onViewChange(view));
|
|
79
|
+
* };
|
|
80
|
+
*
|
|
81
|
+
* return (
|
|
82
|
+
* <div className={transitionClass}>
|
|
83
|
+
* ...
|
|
84
|
+
* </div>
|
|
85
|
+
* );
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function useViewTransition(
|
|
90
|
+
config: ViewTransitionConfig = {},
|
|
91
|
+
): ViewTransitionResult {
|
|
92
|
+
const {
|
|
93
|
+
enabled = true,
|
|
94
|
+
fallbackClass = DEFAULT_FALLBACK_CLASS,
|
|
95
|
+
duration = DEFAULT_DURATION,
|
|
96
|
+
onTransitionStart,
|
|
97
|
+
onTransitionEnd,
|
|
98
|
+
} = config;
|
|
99
|
+
|
|
100
|
+
const reducedMotion = useReducedMotion();
|
|
101
|
+
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
102
|
+
|
|
103
|
+
const isSupported = useMemo(() => supportsViewTransitions(), []);
|
|
104
|
+
|
|
105
|
+
const startTransition = useCallback(
|
|
106
|
+
async (callback: () => void | Promise<void>): Promise<void> => {
|
|
107
|
+
// Skip transition when disabled or user prefers reduced motion
|
|
108
|
+
if (!enabled || reducedMotion) {
|
|
109
|
+
await callback();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onTransitionStart?.();
|
|
114
|
+
setIsTransitioning(true);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
if (isSupported) {
|
|
118
|
+
// Use native View Transitions API
|
|
119
|
+
const transition = (document as Document & {
|
|
120
|
+
startViewTransition: (cb: () => void | Promise<void>) => ViewTransition;
|
|
121
|
+
}).startViewTransition(callback);
|
|
122
|
+
await transition.finished;
|
|
123
|
+
} else {
|
|
124
|
+
// Fallback: apply class, run callback, remove class after duration
|
|
125
|
+
await callback();
|
|
126
|
+
await new Promise<void>((resolve) => setTimeout(resolve, duration));
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
setIsTransitioning(false);
|
|
130
|
+
onTransitionEnd?.();
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
[enabled, reducedMotion, isSupported, duration, onTransitionStart, onTransitionEnd],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const transitionClass = isTransitioning && !isSupported ? fallbackClass : '';
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
isSupported,
|
|
140
|
+
isTransitioning,
|
|
141
|
+
startTransition,
|
|
142
|
+
transitionClass,
|
|
143
|
+
};
|
|
144
|
+
}
|