@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.
Files changed (89) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/dist/SchemaRenderer.d.ts +20 -1
  4. package/dist/SchemaRenderer.js +76 -13
  5. package/dist/context/DndContext.d.ts +135 -0
  6. package/dist/context/DndContext.js +120 -0
  7. package/dist/context/NotificationContext.d.ts +113 -0
  8. package/dist/context/NotificationContext.js +130 -0
  9. package/dist/context/index.d.ts +2 -0
  10. package/dist/context/index.js +2 -0
  11. package/dist/hooks/index.d.ts +17 -0
  12. package/dist/hooks/index.js +17 -0
  13. package/dist/hooks/useAnimation.d.ts +46 -0
  14. package/dist/hooks/useAnimation.js +59 -0
  15. package/dist/hooks/useClientNotifications.d.ts +53 -0
  16. package/dist/hooks/useClientNotifications.js +190 -0
  17. package/dist/hooks/useColumnSummary.d.ts +38 -0
  18. package/dist/hooks/useColumnSummary.js +54 -0
  19. package/dist/hooks/useCrudShortcuts.d.ts +39 -0
  20. package/dist/hooks/useCrudShortcuts.js +96 -0
  21. package/dist/hooks/useDensityMode.d.ts +48 -0
  22. package/dist/hooks/useDensityMode.js +60 -0
  23. package/dist/hooks/useETagCache.d.ts +75 -0
  24. package/dist/hooks/useETagCache.js +306 -0
  25. package/dist/hooks/useFocusManagement.d.ts +89 -0
  26. package/dist/hooks/useFocusManagement.js +188 -0
  27. package/dist/hooks/useFocusTrap.d.ts +41 -0
  28. package/dist/hooks/useFocusTrap.js +103 -0
  29. package/dist/hooks/useKeyboardShortcuts.d.ts +49 -0
  30. package/dist/hooks/useKeyboardShortcuts.js +86 -0
  31. package/dist/hooks/useOffline.d.ts +96 -0
  32. package/dist/hooks/useOffline.js +185 -0
  33. package/dist/hooks/usePageTransition.d.ts +60 -0
  34. package/dist/hooks/usePageTransition.js +111 -0
  35. package/dist/hooks/usePerformance.d.ts +87 -0
  36. package/dist/hooks/usePerformance.js +165 -0
  37. package/dist/hooks/usePerformanceBudget.d.ts +78 -0
  38. package/dist/hooks/usePerformanceBudget.js +209 -0
  39. package/dist/hooks/useReducedMotion.d.ts +28 -0
  40. package/dist/hooks/useReducedMotion.js +48 -0
  41. package/dist/hooks/useSchemaPersistence.d.ts +87 -0
  42. package/dist/hooks/useSchemaPersistence.js +173 -0
  43. package/dist/hooks/useViewSharing.d.ts +57 -0
  44. package/dist/hooks/useViewSharing.js +74 -0
  45. package/dist/hooks/useViewTransition.d.ts +54 -0
  46. package/dist/hooks/useViewTransition.js +83 -0
  47. package/package.json +7 -7
  48. package/src/SchemaRenderer.tsx +122 -13
  49. package/src/__tests__/SchemaRenderer.aria.test.tsx +118 -0
  50. package/src/__tests__/SchemaRenderer.errorBoundary.test.tsx +114 -0
  51. package/src/__tests__/SchemaRendererProvider.smoke.test.tsx +132 -0
  52. package/src/__tests__/useOffline.test.ts +198 -0
  53. package/src/__tests__/usePageTransition.test.ts +214 -0
  54. package/src/__tests__/usePerformance.test.ts +137 -0
  55. package/src/__tests__/view-switching.test.tsx +456 -0
  56. package/src/context/DndContext.tsx +260 -0
  57. package/src/context/NotificationContext.tsx +280 -0
  58. package/src/context/__tests__/DndContext.test.tsx +150 -0
  59. package/src/context/__tests__/NotificationContext.test.tsx +176 -0
  60. package/src/context/index.ts +2 -0
  61. package/src/hooks/__tests__/useAnimation.test.ts +82 -0
  62. package/src/hooks/__tests__/useColumnSummary.test.ts +93 -0
  63. package/src/hooks/__tests__/useDensityMode.test.ts +81 -0
  64. package/src/hooks/__tests__/useETagCache.test.ts +153 -0
  65. package/src/hooks/__tests__/useFocusTrap.test.ts +47 -0
  66. package/src/hooks/__tests__/useKeyboardShortcuts.test.ts +144 -0
  67. package/src/hooks/__tests__/usePerformanceBudget.test.ts +157 -0
  68. package/src/hooks/__tests__/useSchemaPersistence.test.ts +220 -0
  69. package/src/hooks/__tests__/useViewSharing.test.ts +121 -0
  70. package/src/hooks/__tests__/useViewTransition.test.ts +61 -0
  71. package/src/hooks/index.ts +17 -1
  72. package/src/hooks/useAnimation.ts +117 -0
  73. package/src/hooks/useClientNotifications.ts +251 -0
  74. package/src/hooks/useColumnSummary.ts +87 -0
  75. package/src/hooks/useCrudShortcuts.ts +126 -0
  76. package/src/hooks/useDensityMode.ts +102 -0
  77. package/src/hooks/useETagCache.ts +404 -0
  78. package/src/hooks/useFocusManagement.ts +248 -0
  79. package/src/hooks/useFocusTrap.ts +138 -0
  80. package/src/hooks/useKeyboardShortcuts.ts +119 -0
  81. package/src/hooks/useOffline.ts +301 -0
  82. package/src/hooks/usePageTransition.ts +187 -0
  83. package/src/hooks/usePerformance.ts +246 -0
  84. package/src/hooks/usePerformanceBudget.ts +313 -0
  85. package/src/hooks/useReducedMotion.ts +53 -0
  86. package/src/hooks/useSchemaPersistence.ts +222 -0
  87. package/src/hooks/useViewSharing.ts +140 -0
  88. package/src/hooks/useViewTransition.ts +144 -0
  89. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
1
 
2
- > @object-ui/react@2.0.0 build /home/runner/work/objectui/objectui/packages/react
2
+ > @object-ui/react@3.0.0 build /home/runner/work/objectui/objectui/packages/react
3
3
  > tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @object-ui/react
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 87979c3: Upgrade to @objectstack v3.0.0 and console bundle optimization
8
+ - Upgraded all @objectstack/\* packages from ^2.0.7 to ^3.0.0
9
+ - Breaking change migrations: Hub → Cloud namespace, definePlugin removed, PaginatedResult.value → .records, PaginatedResult.count → .total, client.meta.getObject() → client.meta.getItem()
10
+ - Console bundle optimization: split monolithic 3.7 MB chunk into 17 granular cacheable chunks (95% main entry reduction)
11
+ - Added gzip + brotli pre-compression via vite-plugin-compression2
12
+ - Lazy MSW loading for build:server (~150 KB gzip saved)
13
+ - Added bundle analysis with rollup-plugin-visualizer
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [87979c3]
18
+ - @object-ui/types@3.0.0
19
+ - @object-ui/core@3.0.0
20
+ - @object-ui/i18n@3.0.0
21
+
3
22
  ## 2.0.0
4
23
 
5
24
  ### Major Changes
@@ -5,8 +5,27 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- import React from 'react';
8
+ import React, { Component } from 'react';
9
9
  import { SchemaNode } from '@object-ui/core';
10
+ /**
11
+ * Per-component Error Boundary for SchemaRenderer.
12
+ * Catches render errors in individual components, preventing one broken
13
+ * component from crashing the entire page.
14
+ */
15
+ interface SchemaErrorBoundaryState {
16
+ hasError: boolean;
17
+ error: Error | null;
18
+ }
19
+ export declare class SchemaErrorBoundary extends Component<{
20
+ componentType?: string;
21
+ children: React.ReactNode;
22
+ }, SchemaErrorBoundaryState> {
23
+ state: SchemaErrorBoundaryState;
24
+ static getDerivedStateFromError(error: Error): SchemaErrorBoundaryState;
25
+ handleRetry: () => void;
26
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
27
+ }
10
28
  export declare const SchemaRenderer: React.ForwardRefExoticComponent<Omit<{
11
29
  schema: SchemaNode;
12
30
  } & Record<string, any>, "ref"> & React.RefAttributes<any>>;
31
+ export {};
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * ObjectUI
4
4
  * Copyright (c) 2024-present ObjectStack Inc.
@@ -6,9 +6,63 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
6
6
  * This source code is licensed under the MIT license found in the
7
7
  * LICENSE file in the root directory of this source tree.
8
8
  */
9
- import React, { forwardRef, useContext, useMemo } from 'react';
10
- import { ComponentRegistry, ExpressionEvaluator } from '@object-ui/core';
9
+ import React, { forwardRef, useContext, useMemo, Component } from 'react';
10
+ import { ComponentRegistry, ExpressionEvaluator, isObjectUIError, ERROR_CODES, debugLog, debugTime, debugTimeEnd, } from '@object-ui/core';
11
11
  import { SchemaRendererContext } from './context/SchemaRendererContext';
12
+ import { resolveI18nLabel } from './utils/i18n';
13
+ /**
14
+ * Extract AriaPropsSchema properties from a schema node and convert
15
+ * them to standard HTML ARIA attributes.
16
+ *
17
+ * @objectstack/spec AriaPropsSchema defines:
18
+ * ariaLabel: string | I18nLabel (→ aria-label)
19
+ * ariaDescribedBy: string (→ aria-describedby)
20
+ * role: string (→ role)
21
+ */
22
+ function resolveAriaProps(schema) {
23
+ const aria = {};
24
+ if (schema.ariaLabel) {
25
+ aria['aria-label'] = resolveI18nLabel(schema.ariaLabel);
26
+ }
27
+ if (schema.ariaDescribedBy) {
28
+ aria['aria-describedby'] = schema.ariaDescribedBy;
29
+ }
30
+ if (schema.role) {
31
+ aria['role'] = schema.role;
32
+ }
33
+ return aria;
34
+ }
35
+ export class SchemaErrorBoundary extends Component {
36
+ constructor() {
37
+ super(...arguments);
38
+ Object.defineProperty(this, "state", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: { hasError: false, error: null }
43
+ });
44
+ Object.defineProperty(this, "handleRetry", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: () => {
49
+ this.setState({ hasError: false, error: null });
50
+ }
51
+ });
52
+ }
53
+ static getDerivedStateFromError(error) {
54
+ return { hasError: true, error };
55
+ }
56
+ render() {
57
+ if (this.state.hasError && this.state.error) {
58
+ const error = this.state.error;
59
+ const isDev = process.env.NODE_ENV !== 'production';
60
+ const objuiError = isObjectUIError(error) ? error : null;
61
+ return (_jsxs("div", { className: "p-4 border border-orange-400 rounded bg-orange-50 text-orange-700 my-2", role: "alert", children: [_jsxs("p", { className: "font-medium", children: ["Component", this.props.componentType ? ` "${this.props.componentType}"` : '', " failed to render"] }), _jsx("p", { className: "text-sm mt-1", children: error.message }), isDev && objuiError?.code && (_jsxs("p", { className: "text-xs mt-1 text-orange-500", children: ["Error code: ", objuiError.code, objuiError.details?.suggestion ? (_jsxs("span", { className: "block mt-0.5", children: ["\uD83D\uDCA1 ", String(objuiError.details.suggestion)] })) : null] })), _jsx("button", { onClick: this.handleRetry, className: "mt-2 text-sm underline hover:no-underline", children: "Retry" })] }));
62
+ }
63
+ return this.props.children;
64
+ }
65
+ }
12
66
  export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
13
67
  const context = useContext(SchemaRendererContext);
14
68
  const dataSource = context?.dataSource || {};
@@ -43,22 +97,31 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
43
97
  // If schema is just a string, render it as text
44
98
  if (typeof evaluatedSchema === 'string')
45
99
  return _jsx(_Fragment, { children: evaluatedSchema });
100
+ debugLog('schema', 'Rendering schema node', { type: evaluatedSchema.type, id: evaluatedSchema.id });
46
101
  const Component = ComponentRegistry.get(evaluatedSchema.type);
47
102
  if (!Component) {
48
- return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", children: ["Unknown component type: ", _jsx("strong", { children: evaluatedSchema.type }), _jsx("pre", { className: "text-xs mt-2 overflow-auto", children: JSON.stringify(evaluatedSchema, null, 2) })] }));
103
+ debugLog('schema', 'Component not found in registry', { type: evaluatedSchema.type });
104
+ const errorInfo = ERROR_CODES['OBJUI-001'];
105
+ return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", role: "alert", children: [_jsxs("p", { className: "font-medium", children: ["Unknown component type: ", _jsx("strong", { children: evaluatedSchema.type })] }), process.env.NODE_ENV !== 'production' && (_jsxs("p", { className: "text-xs mt-1", children: ["\uD83D\uDCA1 ", errorInfo.suggestion, " (OBJUI-001)"] })), _jsx("pre", { className: "text-xs mt-2 overflow-auto", children: JSON.stringify(evaluatedSchema, null, 2) })] }));
49
106
  }
50
107
  // Note: We don't forward the ref to the Component because components in the registry
51
108
  // may not support refs. The SchemaRenderer itself can still receive refs for its own use.
52
109
  // Extract schema metadata properties that should NOT be passed as React props
53
110
  const { type: _type, children: _children, body: _body, schema: _schema, visible: _visible, visibleOn: _visibleOn, hidden: _hidden, hiddenOn: _hiddenOn, disabled: _disabled, disabledOn: _disabledOn, ...componentProps } = evaluatedSchema;
54
- return React.createElement(Component, {
55
- schema: evaluatedSchema,
56
- ...componentProps, // Spread non-metadata schema properties as props
57
- ...(evaluatedSchema.props || {}), // Override with explicit props if provided
58
- className: evaluatedSchema.className,
59
- 'data-obj-id': evaluatedSchema.id,
60
- 'data-obj-type': evaluatedSchema.type,
61
- ...props
62
- });
111
+ // Extract AriaPropsSchema properties for accessibility
112
+ const ariaProps = resolveAriaProps(evaluatedSchema);
113
+ debugTime(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
114
+ const rendered = (_jsx(SchemaErrorBoundary, { componentType: evaluatedSchema.type, children: React.createElement(Component, {
115
+ schema: evaluatedSchema,
116
+ ...componentProps, // Spread non-metadata schema properties as props
117
+ ...(evaluatedSchema.props || {}), // Override with explicit props if provided
118
+ ...ariaProps, // Inject ARIA attributes from AriaPropsSchema
119
+ className: evaluatedSchema.className,
120
+ 'data-obj-id': evaluatedSchema.id,
121
+ 'data-obj-type': evaluatedSchema.type,
122
+ ...props
123
+ }) }));
124
+ debugTimeEnd(`render:${evaluatedSchema.type}:${evaluatedSchema.id ?? 'anon'}`);
125
+ return rendered;
63
126
  });
64
127
  SchemaRenderer.displayName = 'SchemaRenderer';
@@ -0,0 +1,135 @@
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
+ * @object-ui/react - Drag and Drop Context
10
+ *
11
+ * Provides a unified DnD configuration system to the component tree.
12
+ * Implements DndConfigSchema, DragItemSchema, DropZoneSchema,
13
+ * DragConstraintSchema, and DropEffectSchema from @objectstack/spec v2.0.7.
14
+ */
15
+ import React from 'react';
16
+ /** Drop effect types aligned with DropEffectSchema */
17
+ export type DropEffectType = 'move' | 'copy' | 'link' | 'none';
18
+ /** Drag constraint axis aligned with DragConstraintSchema */
19
+ export type DragAxis = 'x' | 'y' | 'both';
20
+ /** Drag item metadata */
21
+ export interface DragItemData {
22
+ /** Unique identifier for the dragged item */
23
+ id: string;
24
+ /** Type of the dragged item (for drop zone filtering) */
25
+ type: string;
26
+ /** Arbitrary data associated with the drag */
27
+ data?: Record<string, unknown>;
28
+ /** Source container/zone identifier */
29
+ sourceId?: string;
30
+ }
31
+ /** Drop zone configuration */
32
+ export interface DropZoneConfig {
33
+ /** Unique identifier for the drop zone */
34
+ id: string;
35
+ /** Accepted drag item types */
36
+ acceptTypes: string[];
37
+ /** Drop effect to apply */
38
+ effect?: DropEffectType;
39
+ /** Whether the zone is currently disabled */
40
+ disabled?: boolean;
41
+ /** Maximum number of items this zone can hold */
42
+ maxItems?: number;
43
+ }
44
+ /** Drag constraints configuration */
45
+ export interface DragConstraintConfig {
46
+ /** Constrain movement to an axis */
47
+ axis?: DragAxis;
48
+ /** Minimum distance before drag starts (in pixels) */
49
+ activationDistance?: number;
50
+ /** Whether to show a drag handle */
51
+ showHandle?: boolean;
52
+ /** Container bounds (CSS selector) */
53
+ containerId?: string;
54
+ }
55
+ /** Overall DnD configuration aligned with DndConfigSchema */
56
+ export interface DndSystemConfig {
57
+ /** Whether DnD is enabled */
58
+ enabled?: boolean;
59
+ /** Default drag constraints */
60
+ defaultConstraints?: DragConstraintConfig;
61
+ /** Whether to show drag overlay/preview */
62
+ showPreview?: boolean;
63
+ /** Auto-scroll when dragging near edges */
64
+ autoScroll?: boolean;
65
+ }
66
+ /** Drag event information */
67
+ export interface DragEvent {
68
+ /** The dragged item */
69
+ item: DragItemData;
70
+ /** Target drop zone (null if not over a zone) */
71
+ targetZoneId: string | null;
72
+ /** The applied drop effect */
73
+ effect: DropEffectType;
74
+ }
75
+ export interface DndProviderProps {
76
+ children: React.ReactNode;
77
+ /** System-wide DnD configuration */
78
+ config?: DndSystemConfig;
79
+ /** Called when a drag starts */
80
+ onDragStart?: (event: DragEvent) => void;
81
+ /** Called when an item is dropped */
82
+ onDrop?: (event: DragEvent) => void;
83
+ /** Called when a drag is cancelled */
84
+ onDragCancel?: (event: DragEvent) => void;
85
+ }
86
+ interface DndContextValue {
87
+ /** System-wide DnD configuration */
88
+ config: DndSystemConfig;
89
+ /** Whether DnD is enabled */
90
+ enabled: boolean;
91
+ /** Currently dragging item (null if not dragging) */
92
+ activeItem: DragItemData | null;
93
+ /** Registered drop zones */
94
+ dropZones: Map<string, DropZoneConfig>;
95
+ /** Start a drag operation */
96
+ startDrag: (item: DragItemData) => void;
97
+ /** End the current drag operation */
98
+ endDrag: (targetZoneId?: string) => void;
99
+ /** Cancel the current drag */
100
+ cancelDrag: () => void;
101
+ /** Register a drop zone */
102
+ registerDropZone: (zone: DropZoneConfig) => void;
103
+ /** Unregister a drop zone */
104
+ unregisterDropZone: (zoneId: string) => void;
105
+ /** Check if a drop zone accepts a drag item type */
106
+ canDrop: (zoneId: string, itemType: string) => boolean;
107
+ }
108
+ /**
109
+ * DndProvider — Provides a unified DnD system aligned with @objectstack/spec v2.0.7.
110
+ *
111
+ * This is a coordination layer; actual drag mechanics are delegated to
112
+ * libraries like @dnd-kit/core or react-grid-layout in individual plugins.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * <DndProvider
117
+ * config={{ enabled: true, showPreview: true, autoScroll: true }}
118
+ * onDrop={(event) => handleDrop(event.item, event.targetZoneId)}
119
+ * >
120
+ * <KanbanBoard />
121
+ * </DndProvider>
122
+ * ```
123
+ */
124
+ export declare const DndProvider: React.FC<DndProviderProps>;
125
+ /**
126
+ * Hook to consume the DnD context.
127
+ *
128
+ * @throws Error if used outside a DndProvider
129
+ */
130
+ export declare function useDnd(): DndContextValue;
131
+ /**
132
+ * Hook to check if a DndProvider is available.
133
+ */
134
+ export declare function useHasDndProvider(): boolean;
135
+ export {};
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ObjectUI
4
+ * Copyright (c) 2024-present ObjectStack Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ /**
10
+ * @object-ui/react - Drag and Drop Context
11
+ *
12
+ * Provides a unified DnD configuration system to the component tree.
13
+ * Implements DndConfigSchema, DragItemSchema, DropZoneSchema,
14
+ * DragConstraintSchema, and DropEffectSchema from @objectstack/spec v2.0.7.
15
+ */
16
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react';
17
+ const DndCtx = createContext(null);
18
+ /**
19
+ * DndProvider — Provides a unified DnD system aligned with @objectstack/spec v2.0.7.
20
+ *
21
+ * This is a coordination layer; actual drag mechanics are delegated to
22
+ * libraries like @dnd-kit/core or react-grid-layout in individual plugins.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * <DndProvider
27
+ * config={{ enabled: true, showPreview: true, autoScroll: true }}
28
+ * onDrop={(event) => handleDrop(event.item, event.targetZoneId)}
29
+ * >
30
+ * <KanbanBoard />
31
+ * </DndProvider>
32
+ * ```
33
+ */
34
+ export const DndProvider = ({ children, config: userConfig = {}, onDragStart, onDrop, onDragCancel, }) => {
35
+ const config = useMemo(() => ({
36
+ enabled: true,
37
+ showPreview: true,
38
+ autoScroll: true,
39
+ ...userConfig,
40
+ }),
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ [JSON.stringify(userConfig)]);
43
+ const [activeItem, setActiveItem] = useState(null);
44
+ const [dropZones] = useState(() => new Map());
45
+ const startDrag = useCallback((item) => {
46
+ if (!config.enabled)
47
+ return;
48
+ setActiveItem(item);
49
+ onDragStart?.({ item, targetZoneId: null, effect: 'move' });
50
+ }, [config.enabled, onDragStart]);
51
+ const endDrag = useCallback((targetZoneId) => {
52
+ if (!activeItem)
53
+ return;
54
+ const zone = targetZoneId ? dropZones.get(targetZoneId) : undefined;
55
+ const effect = zone?.effect ?? 'move';
56
+ onDrop?.({ item: activeItem, targetZoneId: targetZoneId ?? null, effect });
57
+ setActiveItem(null);
58
+ }, [activeItem, dropZones, onDrop]);
59
+ const cancelDrag = useCallback(() => {
60
+ if (!activeItem)
61
+ return;
62
+ onDragCancel?.({ item: activeItem, targetZoneId: null, effect: 'none' });
63
+ setActiveItem(null);
64
+ }, [activeItem, onDragCancel]);
65
+ const registerDropZone = useCallback((zone) => {
66
+ dropZones.set(zone.id, zone);
67
+ }, [dropZones]);
68
+ const unregisterDropZone = useCallback((zoneId) => {
69
+ dropZones.delete(zoneId);
70
+ }, [dropZones]);
71
+ const canDrop = useCallback((zoneId, itemType) => {
72
+ const zone = dropZones.get(zoneId);
73
+ if (!zone || zone.disabled)
74
+ return false;
75
+ return zone.acceptTypes.includes(itemType);
76
+ }, [dropZones]);
77
+ const value = useMemo(() => ({
78
+ config,
79
+ enabled: config.enabled ?? true,
80
+ activeItem,
81
+ dropZones,
82
+ startDrag,
83
+ endDrag,
84
+ cancelDrag,
85
+ registerDropZone,
86
+ unregisterDropZone,
87
+ canDrop,
88
+ }), [
89
+ config,
90
+ activeItem,
91
+ dropZones,
92
+ startDrag,
93
+ endDrag,
94
+ cancelDrag,
95
+ registerDropZone,
96
+ unregisterDropZone,
97
+ canDrop,
98
+ ]);
99
+ return _jsx(DndCtx.Provider, { value: value, children: children });
100
+ };
101
+ DndProvider.displayName = 'DndProvider';
102
+ /**
103
+ * Hook to consume the DnD context.
104
+ *
105
+ * @throws Error if used outside a DndProvider
106
+ */
107
+ export function useDnd() {
108
+ const ctx = useContext(DndCtx);
109
+ if (!ctx) {
110
+ throw new Error('useDnd must be used within a <DndProvider>. ' +
111
+ 'Wrap your app with <DndProvider> to use the drag and drop system.');
112
+ }
113
+ return ctx;
114
+ }
115
+ /**
116
+ * Hook to check if a DndProvider is available.
117
+ */
118
+ export function useHasDndProvider() {
119
+ return useContext(DndCtx) !== null;
120
+ }
@@ -0,0 +1,113 @@
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
+ * @object-ui/react - Notification Context
10
+ *
11
+ * Provides a spec-driven notification system to the component tree.
12
+ * Implements NotificationSchema, NotificationConfigSchema, and
13
+ * NotificationActionSchema from @objectstack/spec v2.0.7.
14
+ */
15
+ import React from 'react';
16
+ /** Notification severity levels aligned with NotificationSeveritySchema */
17
+ export type NotificationSeverityLevel = 'info' | 'success' | 'warning' | 'error';
18
+ /** Notification display type aligned with NotificationTypeSchema */
19
+ export type NotificationDisplayType = 'toast' | 'banner' | 'snackbar' | 'modal';
20
+ /** Notification position aligned with NotificationPositionSchema */
21
+ export type NotificationPositionValue = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
22
+ /** Action button on a notification */
23
+ export interface NotificationActionButton {
24
+ label: string;
25
+ onClick: () => void;
26
+ variant?: 'default' | 'destructive' | 'outline';
27
+ }
28
+ /** A single notification item */
29
+ export interface NotificationItem {
30
+ id: string;
31
+ title: string;
32
+ message?: string;
33
+ severity: NotificationSeverityLevel;
34
+ displayType?: NotificationDisplayType;
35
+ actions?: NotificationActionButton[];
36
+ /** Duration in ms (0 = persistent). Default: 5000 */
37
+ duration?: number;
38
+ /** Whether the notification has been read */
39
+ read?: boolean;
40
+ /** Timestamp */
41
+ createdAt: Date;
42
+ /** Optional icon name (lucide icon) */
43
+ icon?: string;
44
+ }
45
+ /** Configuration for the notification system */
46
+ export interface NotificationSystemConfig {
47
+ /** Default position for toast notifications */
48
+ position?: NotificationPositionValue;
49
+ /** Default duration in ms */
50
+ defaultDuration?: number;
51
+ /** Maximum number of visible notifications */
52
+ maxVisible?: number;
53
+ /** Whether to stack notifications */
54
+ stacking?: boolean;
55
+ }
56
+ export interface NotificationProviderProps {
57
+ children: React.ReactNode;
58
+ /** System configuration */
59
+ config?: NotificationSystemConfig;
60
+ /** External toast handler (e.g., Sonner) for rendering toasts */
61
+ onToast?: (notification: NotificationItem) => void;
62
+ }
63
+ interface NotificationContextValue {
64
+ /** All notifications (including history) */
65
+ notifications: NotificationItem[];
66
+ /** Unread notification count */
67
+ unreadCount: number;
68
+ /** Add a notification */
69
+ notify: (notification: Omit<NotificationItem, 'id' | 'createdAt' | 'read'>) => string;
70
+ /** Convenience: show info notification */
71
+ info: (title: string, message?: string) => string;
72
+ /** Convenience: show success notification */
73
+ success: (title: string, message?: string) => string;
74
+ /** Convenience: show warning notification */
75
+ warning: (title: string, message?: string) => string;
76
+ /** Convenience: show error notification */
77
+ error: (title: string, message?: string) => string;
78
+ /** Mark a notification as read */
79
+ markAsRead: (id: string) => void;
80
+ /** Mark all notifications as read */
81
+ markAllAsRead: () => void;
82
+ /** Dismiss a notification */
83
+ dismiss: (id: string) => void;
84
+ /** Clear all notifications */
85
+ clearAll: () => void;
86
+ /** System configuration */
87
+ config: NotificationSystemConfig;
88
+ }
89
+ /**
90
+ * NotificationProvider — Provides a spec-driven notification system.
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * <NotificationProvider
95
+ * config={{ position: 'top-right', defaultDuration: 5000, maxVisible: 5 }}
96
+ * onToast={(n) => toast[n.severity](n.title, { description: n.message })}
97
+ * >
98
+ * <App />
99
+ * </NotificationProvider>
100
+ * ```
101
+ */
102
+ export declare const NotificationProvider: React.FC<NotificationProviderProps>;
103
+ /**
104
+ * Hook to consume the NotificationProvider context.
105
+ *
106
+ * @throws Error if used outside a NotificationProvider
107
+ */
108
+ export declare function useNotifications(): NotificationContextValue;
109
+ /**
110
+ * Hook to check if a NotificationProvider is available.
111
+ */
112
+ export declare function useHasNotificationProvider(): boolean;
113
+ export {};
@@ -0,0 +1,130 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ObjectUI
4
+ * Copyright (c) 2024-present ObjectStack Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ /**
10
+ * @object-ui/react - Notification Context
11
+ *
12
+ * Provides a spec-driven notification system to the component tree.
13
+ * Implements NotificationSchema, NotificationConfigSchema, and
14
+ * NotificationActionSchema from @objectstack/spec v2.0.7.
15
+ */
16
+ import { createContext, useCallback, useContext, useMemo, useState } from 'react';
17
+ let notificationCounter = 0;
18
+ const NotificationCtx = createContext(null);
19
+ /**
20
+ * NotificationProvider — Provides a spec-driven notification system.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * <NotificationProvider
25
+ * config={{ position: 'top-right', defaultDuration: 5000, maxVisible: 5 }}
26
+ * onToast={(n) => toast[n.severity](n.title, { description: n.message })}
27
+ * >
28
+ * <App />
29
+ * </NotificationProvider>
30
+ * ```
31
+ */
32
+ export const NotificationProvider = ({ children, config: userConfig = {}, onToast, }) => {
33
+ const config = useMemo(() => ({
34
+ position: 'top-right',
35
+ defaultDuration: 5000,
36
+ maxVisible: 5,
37
+ stacking: true,
38
+ ...userConfig,
39
+ }),
40
+ // eslint-disable-next-line react-hooks/exhaustive-deps
41
+ [JSON.stringify(userConfig)]);
42
+ const [notifications, setNotifications] = useState([]);
43
+ const notify = useCallback((input) => {
44
+ const id = `notification-${++notificationCounter}`;
45
+ const notification = {
46
+ ...input,
47
+ id,
48
+ createdAt: new Date(),
49
+ read: false,
50
+ };
51
+ setNotifications((prev) => [notification, ...prev]);
52
+ // Delegate to external toast handler if provided
53
+ if (onToast) {
54
+ onToast(notification);
55
+ }
56
+ // Auto-dismiss non-persistent notifications
57
+ const duration = input.duration ?? config.defaultDuration ?? 5000;
58
+ if (duration > 0) {
59
+ setTimeout(() => {
60
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
61
+ }, duration);
62
+ }
63
+ return id;
64
+ }, [config.defaultDuration, onToast]);
65
+ const info = useCallback((title, message) => notify({ title, message, severity: 'info' }), [notify]);
66
+ const success = useCallback((title, message) => notify({ title, message, severity: 'success' }), [notify]);
67
+ const warning = useCallback((title, message) => notify({ title, message, severity: 'warning' }), [notify]);
68
+ const error = useCallback((title, message) => notify({ title, message, severity: 'error' }), [notify]);
69
+ const markAsRead = useCallback((id) => {
70
+ setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n)));
71
+ }, []);
72
+ const markAllAsRead = useCallback(() => {
73
+ setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
74
+ }, []);
75
+ const dismiss = useCallback((id) => {
76
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
77
+ }, []);
78
+ const clearAll = useCallback(() => {
79
+ setNotifications([]);
80
+ }, []);
81
+ const unreadCount = useMemo(() => notifications.filter((n) => !n.read).length, [notifications]);
82
+ const value = useMemo(() => ({
83
+ notifications,
84
+ unreadCount,
85
+ notify,
86
+ info,
87
+ success,
88
+ warning,
89
+ error,
90
+ markAsRead,
91
+ markAllAsRead,
92
+ dismiss,
93
+ clearAll,
94
+ config,
95
+ }), [
96
+ notifications,
97
+ unreadCount,
98
+ notify,
99
+ info,
100
+ success,
101
+ warning,
102
+ error,
103
+ markAsRead,
104
+ markAllAsRead,
105
+ dismiss,
106
+ clearAll,
107
+ config,
108
+ ]);
109
+ return (_jsx(NotificationCtx.Provider, { value: value, children: children }));
110
+ };
111
+ NotificationProvider.displayName = 'NotificationProvider';
112
+ /**
113
+ * Hook to consume the NotificationProvider context.
114
+ *
115
+ * @throws Error if used outside a NotificationProvider
116
+ */
117
+ export function useNotifications() {
118
+ const ctx = useContext(NotificationCtx);
119
+ if (!ctx) {
120
+ throw new Error('useNotifications must be used within a <NotificationProvider>. ' +
121
+ 'Wrap your app with <NotificationProvider> to use the notification system.');
122
+ }
123
+ return ctx;
124
+ }
125
+ /**
126
+ * Hook to check if a NotificationProvider is available.
127
+ */
128
+ export function useHasNotificationProvider() {
129
+ return useContext(NotificationCtx) !== null;
130
+ }
@@ -8,3 +8,5 @@
8
8
  export * from './SchemaRendererContext';
9
9
  export * from './ActionContext';
10
10
  export * from './ThemeContext';
11
+ export * from './NotificationContext';
12
+ export * from './DndContext';