@usesidekick/react 0.1.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/README.md +246 -0
- package/dist/index.d.mts +358 -0
- package/dist/index.d.ts +358 -0
- package/dist/index.js +2470 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2403 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jsx-dev-runtime.d.mts +21 -0
- package/dist/jsx-dev-runtime.d.ts +21 -0
- package/dist/jsx-dev-runtime.js +160 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-dev-runtime.mjs +122 -0
- package/dist/jsx-dev-runtime.mjs.map +1 -0
- package/dist/jsx-runtime.d.mts +26 -0
- package/dist/jsx-runtime.d.ts +26 -0
- package/dist/jsx-runtime.js +150 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/jsx-runtime.mjs +109 -0
- package/dist/jsx-runtime.mjs.map +1 -0
- package/dist/server/index.d.mts +235 -0
- package/dist/server/index.d.ts +235 -0
- package/dist/server/index.js +642 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +597 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +64 -0
- package/src/components/SidekickPanel.tsx +868 -0
- package/src/components/index.ts +1 -0
- package/src/context.tsx +157 -0
- package/src/flags.ts +47 -0
- package/src/index.ts +71 -0
- package/src/jsx-dev-runtime.ts +138 -0
- package/src/jsx-runtime.ts +159 -0
- package/src/loader.ts +35 -0
- package/src/primitives/behavior.ts +70 -0
- package/src/primitives/data.ts +91 -0
- package/src/primitives/index.ts +3 -0
- package/src/primitives/ui.ts +268 -0
- package/src/provider.tsx +1264 -0
- package/src/runtime-loader.ts +106 -0
- package/src/server/drizzle-adapter.ts +53 -0
- package/src/server/drizzle-schema.ts +16 -0
- package/src/server/generate.ts +578 -0
- package/src/server/handler.ts +343 -0
- package/src/server/index.ts +20 -0
- package/src/server/storage.ts +1 -0
- package/src/server/types.ts +49 -0
- package/src/types.ts +295 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SidekickPanel } from './SidekickPanel';
|
package/src/context.tsx
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, ComponentType } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
SidekickState,
|
|
6
|
+
Override,
|
|
7
|
+
AddedColumn,
|
|
8
|
+
ColumnRename,
|
|
9
|
+
HiddenColumn,
|
|
10
|
+
ColumnOrder,
|
|
11
|
+
MenuItem,
|
|
12
|
+
TabItem,
|
|
13
|
+
ActionItem,
|
|
14
|
+
ValidationRule,
|
|
15
|
+
SortOption,
|
|
16
|
+
GroupByOption,
|
|
17
|
+
KeyboardShortcut,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export interface SidekickContextValue {
|
|
21
|
+
state: SidekickState;
|
|
22
|
+
overrides: Override[];
|
|
23
|
+
isOverrideEnabled: (id: string) => boolean;
|
|
24
|
+
toggleOverride: (id: string) => void;
|
|
25
|
+
// UI getters
|
|
26
|
+
getColumns: (tableId: string) => AddedColumn[];
|
|
27
|
+
getColumnRenames: (tableId: string) => ColumnRename[];
|
|
28
|
+
getHiddenColumns: (tableId: string) => HiddenColumn[];
|
|
29
|
+
getColumnOrder: (tableId: string) => ColumnOrder | undefined;
|
|
30
|
+
getMenuItems: (menuId: string) => MenuItem[];
|
|
31
|
+
getTabs: (tabGroupId: string) => TabItem[];
|
|
32
|
+
getActions: (actionBarId: string) => ActionItem[];
|
|
33
|
+
getValidations: (formId: string) => ValidationRule[];
|
|
34
|
+
getWrapper: (componentName: string) => ((Component: ComponentType) => ComponentType) | undefined;
|
|
35
|
+
getReplacement: (componentName: string) => ComponentType | undefined;
|
|
36
|
+
applyPropsModifiers: (componentId: string, props: Record<string, unknown>) => Record<string, unknown>;
|
|
37
|
+
// Data getters
|
|
38
|
+
getComputedValue: <T>(fieldName: string, data: Record<string, unknown>) => T | undefined;
|
|
39
|
+
applyFilters: <T>(filterName: string, items: T[]) => T[];
|
|
40
|
+
applyTransform: <T>(dataKey: string, data: T) => T;
|
|
41
|
+
getSortOptions: (tableId: string) => SortOption[];
|
|
42
|
+
getGroupByOptions: (tableId: string) => GroupByOption[];
|
|
43
|
+
// Behavior getters
|
|
44
|
+
getKeyboardShortcuts: () => KeyboardShortcut[];
|
|
45
|
+
emitEvent: (eventName: string, payload: Record<string, unknown>) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const SidekickContext = createContext<SidekickContextValue | null>(null);
|
|
49
|
+
|
|
50
|
+
// Safe hook that returns null during SSR instead of throwing
|
|
51
|
+
export function useSidekickSafe(): SidekickContextValue | null {
|
|
52
|
+
return useContext(SidekickContext);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useSidekick(): SidekickContextValue {
|
|
56
|
+
const context = useContext(SidekickContext);
|
|
57
|
+
if (!context) {
|
|
58
|
+
throw new Error('useSidekick must be used within a SidekickProvider');
|
|
59
|
+
}
|
|
60
|
+
return context;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// UI Hooks
|
|
64
|
+
|
|
65
|
+
export function useAddedColumns(tableId: string): AddedColumn[] {
|
|
66
|
+
const context = useSidekickSafe();
|
|
67
|
+
if (!context) return [];
|
|
68
|
+
return context.getColumns(tableId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useColumnRenames(tableId: string): ColumnRename[] {
|
|
72
|
+
const context = useSidekickSafe();
|
|
73
|
+
if (!context) return [];
|
|
74
|
+
return context.getColumnRenames(tableId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function useHiddenColumns(tableId: string): HiddenColumn[] {
|
|
78
|
+
const context = useSidekickSafe();
|
|
79
|
+
if (!context) return [];
|
|
80
|
+
return context.getHiddenColumns(tableId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function useColumnOrder(tableId: string): ColumnOrder | undefined {
|
|
84
|
+
const context = useSidekickSafe();
|
|
85
|
+
if (!context) return undefined;
|
|
86
|
+
return context.getColumnOrder(tableId);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useMenuItems(menuId: string): MenuItem[] {
|
|
90
|
+
const context = useSidekickSafe();
|
|
91
|
+
if (!context) return [];
|
|
92
|
+
return context.getMenuItems(menuId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function useTabs(tabGroupId: string): TabItem[] {
|
|
96
|
+
const context = useSidekickSafe();
|
|
97
|
+
if (!context) return [];
|
|
98
|
+
return context.getTabs(tabGroupId);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function useActions(actionBarId: string): ActionItem[] {
|
|
102
|
+
const context = useSidekickSafe();
|
|
103
|
+
if (!context) return [];
|
|
104
|
+
return context.getActions(actionBarId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useValidations(formId: string): ValidationRule[] {
|
|
108
|
+
const context = useSidekickSafe();
|
|
109
|
+
if (!context) return [];
|
|
110
|
+
return context.getValidations(formId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function usePropsModifier(componentId: string, props: Record<string, unknown>): Record<string, unknown> {
|
|
114
|
+
const context = useSidekickSafe();
|
|
115
|
+
if (!context) return props;
|
|
116
|
+
return context.applyPropsModifiers(componentId, props);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Data Hooks
|
|
120
|
+
|
|
121
|
+
export function useComputedField<T>(fieldName: string, data: Record<string, unknown>): T | undefined {
|
|
122
|
+
const context = useSidekickSafe();
|
|
123
|
+
if (!context) return undefined;
|
|
124
|
+
return context.getComputedValue<T>(fieldName, data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function useFilter<T>(filterName: string, items: T[]): T[] {
|
|
128
|
+
const context = useSidekickSafe();
|
|
129
|
+
if (!context) return items;
|
|
130
|
+
return context.applyFilters(filterName, items);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function useSortOptions(tableId: string): SortOption[] {
|
|
134
|
+
const context = useSidekickSafe();
|
|
135
|
+
if (!context) return [];
|
|
136
|
+
return context.getSortOptions(tableId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function useGroupByOptions(tableId: string): GroupByOption[] {
|
|
140
|
+
const context = useSidekickSafe();
|
|
141
|
+
if (!context) return [];
|
|
142
|
+
return context.getGroupByOptions(tableId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Behavior Hooks
|
|
146
|
+
|
|
147
|
+
export function useKeyboardShortcuts(): KeyboardShortcut[] {
|
|
148
|
+
const context = useSidekickSafe();
|
|
149
|
+
if (!context) return [];
|
|
150
|
+
return context.getKeyboardShortcuts();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function useEventEmitter(): (eventName: string, payload: Record<string, unknown>) => void {
|
|
154
|
+
const context = useSidekickSafe();
|
|
155
|
+
if (!context) return () => {};
|
|
156
|
+
return context.emitEvent;
|
|
157
|
+
}
|
package/src/flags.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const STORAGE_KEY = 'sidekick_flags';
|
|
2
|
+
|
|
3
|
+
export interface FlagState {
|
|
4
|
+
[overrideId: string]: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function loadFlags(): FlagState {
|
|
8
|
+
if (typeof window === 'undefined') {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
14
|
+
return stored ? JSON.parse(stored) : {};
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function saveFlags(flags: FlagState): void {
|
|
21
|
+
if (typeof window === 'undefined') {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(flags));
|
|
27
|
+
} catch {
|
|
28
|
+
console.warn('[Sidekick] Failed to save flags to localStorage');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getFlag(overrideId: string): boolean {
|
|
33
|
+
const flags = loadFlags();
|
|
34
|
+
return flags[overrideId] ?? false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setFlag(overrideId: string, enabled: boolean): void {
|
|
38
|
+
const flags = loadFlags();
|
|
39
|
+
flags[overrideId] = enabled;
|
|
40
|
+
saveFlags(flags);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function toggleFlag(overrideId: string): boolean {
|
|
44
|
+
const current = getFlag(overrideId);
|
|
45
|
+
setFlag(overrideId, !current);
|
|
46
|
+
return !current;
|
|
47
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Provider and Context
|
|
2
|
+
export { SidekickProvider } from './provider';
|
|
3
|
+
export {
|
|
4
|
+
useSidekick,
|
|
5
|
+
useSidekickSafe,
|
|
6
|
+
// UI hooks
|
|
7
|
+
useAddedColumns,
|
|
8
|
+
useColumnRenames,
|
|
9
|
+
useHiddenColumns,
|
|
10
|
+
useColumnOrder,
|
|
11
|
+
useMenuItems,
|
|
12
|
+
useTabs,
|
|
13
|
+
useActions,
|
|
14
|
+
useValidations,
|
|
15
|
+
usePropsModifier,
|
|
16
|
+
// Data hooks
|
|
17
|
+
useComputedField,
|
|
18
|
+
useFilter,
|
|
19
|
+
useSortOptions,
|
|
20
|
+
useGroupByOptions,
|
|
21
|
+
// Behavior hooks
|
|
22
|
+
useKeyboardShortcuts,
|
|
23
|
+
useEventEmitter,
|
|
24
|
+
} from './context';
|
|
25
|
+
|
|
26
|
+
// Components
|
|
27
|
+
export { SidekickPanel } from './components';
|
|
28
|
+
|
|
29
|
+
// Override loader
|
|
30
|
+
export { registerOverride, createOverride, getRegisteredOverrides } from './loader';
|
|
31
|
+
|
|
32
|
+
// Flags
|
|
33
|
+
export { loadFlags, saveFlags, getFlag, setFlag, toggleFlag } from './flags';
|
|
34
|
+
|
|
35
|
+
// JSX runtime configuration (for debugging)
|
|
36
|
+
export { configureJsxRuntime, getSeenComponentsFromJsx, onConfigChange, isJsxRuntimeConfigured } from './jsx-runtime';
|
|
37
|
+
|
|
38
|
+
// Types
|
|
39
|
+
export type {
|
|
40
|
+
SDK,
|
|
41
|
+
UIPrimitives,
|
|
42
|
+
DataPrimitives,
|
|
43
|
+
BehaviorPrimitives,
|
|
44
|
+
Override,
|
|
45
|
+
OverrideManifest,
|
|
46
|
+
OverrideModule,
|
|
47
|
+
WrappedComponent,
|
|
48
|
+
AddedColumn,
|
|
49
|
+
ColumnRename,
|
|
50
|
+
HiddenColumn,
|
|
51
|
+
ColumnOrder,
|
|
52
|
+
RowFilter,
|
|
53
|
+
MenuItem,
|
|
54
|
+
TabItem,
|
|
55
|
+
PropsModifier,
|
|
56
|
+
ActionItem,
|
|
57
|
+
ValidationRule,
|
|
58
|
+
ComputedField,
|
|
59
|
+
DataFilter,
|
|
60
|
+
DataTransform,
|
|
61
|
+
ApiInterceptor,
|
|
62
|
+
SortOption,
|
|
63
|
+
GroupByOption,
|
|
64
|
+
EventHandler,
|
|
65
|
+
KeyboardShortcut,
|
|
66
|
+
RouteModifier,
|
|
67
|
+
DOMModification,
|
|
68
|
+
InjectionPoint,
|
|
69
|
+
DOMEventListener,
|
|
70
|
+
SidekickState,
|
|
71
|
+
} from './types';
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom JSX dev runtime that intercepts component creation.
|
|
3
|
+
* This is used in development mode.
|
|
4
|
+
* Uses React.createElement internally to avoid circular dependency with react/jsx-dev-runtime alias.
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT: Uses the same globalThis-based config as jsx-runtime.ts
|
|
7
|
+
* so all module instances share the same configuration regardless of bundling.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { configureJsxRuntime, getSeenComponentsFromJsx } from './jsx-runtime';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
type WrapperGetter = (name: string) => ((Component: any) => any) | undefined;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
type ReplacementGetter = (name: string) => any | undefined;
|
|
17
|
+
|
|
18
|
+
const GLOBAL_KEY = '__SIDEKICK_JSX_CONFIG__';
|
|
19
|
+
|
|
20
|
+
interface SidekickJsxConfig {
|
|
21
|
+
getWrapper: WrapperGetter | null;
|
|
22
|
+
getReplacement: ReplacementGetter | null;
|
|
23
|
+
listeners: Set<() => void>;
|
|
24
|
+
seenComponents: Set<string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getGlobalConfig(): SidekickJsxConfig {
|
|
28
|
+
const g = globalThis as Record<string, unknown>;
|
|
29
|
+
if (!g[GLOBAL_KEY]) {
|
|
30
|
+
g[GLOBAL_KEY] = {
|
|
31
|
+
getWrapper: null,
|
|
32
|
+
getReplacement: null,
|
|
33
|
+
listeners: new Set(),
|
|
34
|
+
seenComponents: new Set<string>(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return g[GLOBAL_KEY] as SidekickJsxConfig;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Debug mode
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
const getDebug = () => typeof window !== 'undefined' && (window as any).__SIDEKICK_DEBUG__;
|
|
43
|
+
|
|
44
|
+
function getComponentName(type: unknown): string | null {
|
|
45
|
+
if (!type) return null;
|
|
46
|
+
|
|
47
|
+
if (typeof type === 'function') {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
49
|
+
const fn = type as any;
|
|
50
|
+
if (fn.displayName) return fn.displayName;
|
|
51
|
+
if (fn.name) return fn.name;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof type === 'object' && type !== null) {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
const obj = type as any;
|
|
58
|
+
if (obj.displayName) return obj.displayName;
|
|
59
|
+
if (obj.$$typeof) {
|
|
60
|
+
if (obj.type) return getComponentName(obj.type);
|
|
61
|
+
if (obj.render) return getComponentName(obj.render);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
function interceptType(type: any): any {
|
|
70
|
+
if (typeof type === 'function' || (typeof type === 'object' && type !== null)) {
|
|
71
|
+
const name = getComponentName(type);
|
|
72
|
+
const debug = getDebug();
|
|
73
|
+
const config = getGlobalConfig();
|
|
74
|
+
|
|
75
|
+
if (name) {
|
|
76
|
+
if (debug && !config.seenComponents.has(name)) {
|
|
77
|
+
config.seenComponents.add(name);
|
|
78
|
+
console.log(`[Sidekick] Seen component: ${name}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (config.getReplacement) {
|
|
82
|
+
const replacement = config.getReplacement(name);
|
|
83
|
+
if (replacement) {
|
|
84
|
+
if (debug) console.log(`[Sidekick] Replacing: ${name}`);
|
|
85
|
+
return replacement;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (config.getWrapper) {
|
|
90
|
+
const wrapper = config.getWrapper(name);
|
|
91
|
+
if (wrapper) {
|
|
92
|
+
if (debug) console.log(`[Sidekick] Wrapping: ${name}`);
|
|
93
|
+
return wrapper(type);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return type;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Wrapped jsxDEV function - uses React.createElement to avoid circular alias
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
export function jsxDEV(
|
|
105
|
+
type: any,
|
|
106
|
+
props: any,
|
|
107
|
+
key: string | undefined,
|
|
108
|
+
_isStaticChildren: boolean,
|
|
109
|
+
_source: any,
|
|
110
|
+
_self: any
|
|
111
|
+
) {
|
|
112
|
+
const { children, ...restProps } = props || {};
|
|
113
|
+
const finalProps = key !== undefined ? { ...restProps, key } : restProps;
|
|
114
|
+
|
|
115
|
+
// Handle both single child and array of children
|
|
116
|
+
if (Array.isArray(children)) {
|
|
117
|
+
return React.createElement(interceptType(type), finalProps, ...children);
|
|
118
|
+
}
|
|
119
|
+
return React.createElement(interceptType(type), finalProps, children);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Re-export Fragment from React
|
|
123
|
+
export const Fragment = React.Fragment;
|
|
124
|
+
|
|
125
|
+
// Configuration function - configures the shared global state
|
|
126
|
+
export function configureJsxDevRuntime(
|
|
127
|
+
wrapperGetter: WrapperGetter,
|
|
128
|
+
replacementGetter: ReplacementGetter
|
|
129
|
+
) {
|
|
130
|
+
const config = getGlobalConfig();
|
|
131
|
+
config.getWrapper = wrapperGetter;
|
|
132
|
+
config.getReplacement = replacementGetter;
|
|
133
|
+
// Also configure via the production runtime's function (for its listeners)
|
|
134
|
+
configureJsxRuntime(wrapperGetter, replacementGetter);
|
|
135
|
+
if (getDebug()) console.log('[Sidekick] JSX dev runtime configured (global)');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { getSeenComponentsFromJsx };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom JSX runtime that intercepts component creation.
|
|
3
|
+
* Uses React.createElement internally to avoid circular dependency with react/jsx-runtime alias.
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: Uses globalThis to share state between module instances.
|
|
6
|
+
* When bundled with splitting:false, index.mjs and jsx-runtime.mjs each get their own copy.
|
|
7
|
+
* The provider configures the copy in index.mjs, but JSX calls use jsx-runtime.mjs.
|
|
8
|
+
* By storing getters on globalThis, all instances share the same configuration.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
type WrapperGetter = (name: string) => ((Component: any) => any) | undefined;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
type ReplacementGetter = (name: string) => any | undefined;
|
|
17
|
+
type ConfigChangeListener = () => void;
|
|
18
|
+
|
|
19
|
+
const GLOBAL_KEY = '__SIDEKICK_JSX_CONFIG__';
|
|
20
|
+
|
|
21
|
+
interface SidekickJsxConfig {
|
|
22
|
+
getWrapper: WrapperGetter | null;
|
|
23
|
+
getReplacement: ReplacementGetter | null;
|
|
24
|
+
listeners: Set<ConfigChangeListener>;
|
|
25
|
+
seenComponents: Set<string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getGlobalConfig(): SidekickJsxConfig {
|
|
29
|
+
const g = globalThis as Record<string, unknown>;
|
|
30
|
+
if (!g[GLOBAL_KEY]) {
|
|
31
|
+
g[GLOBAL_KEY] = {
|
|
32
|
+
getWrapper: null,
|
|
33
|
+
getReplacement: null,
|
|
34
|
+
listeners: new Set<ConfigChangeListener>(),
|
|
35
|
+
seenComponents: new Set<string>(),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return g[GLOBAL_KEY] as SidekickJsxConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Debug mode - check on each call since it might be set after module load
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
const getDebug = () => typeof window !== 'undefined' && (window as any).__SIDEKICK_DEBUG__;
|
|
44
|
+
|
|
45
|
+
function getComponentName(type: unknown): string | null {
|
|
46
|
+
if (!type) return null;
|
|
47
|
+
|
|
48
|
+
if (typeof type === 'function') {
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
const fn = type as any;
|
|
51
|
+
if (fn.displayName) return fn.displayName;
|
|
52
|
+
if (fn.name) return fn.name;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof type === 'object' && type !== null) {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
const obj = type as any;
|
|
59
|
+
if (obj.displayName) return obj.displayName;
|
|
60
|
+
if (obj.$$typeof) {
|
|
61
|
+
if (obj.type) return getComponentName(obj.type);
|
|
62
|
+
if (obj.render) return getComponentName(obj.render);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
function interceptType(type: any): any {
|
|
71
|
+
if (typeof type === 'function' || (typeof type === 'object' && type !== null)) {
|
|
72
|
+
const name = getComponentName(type);
|
|
73
|
+
const debug = getDebug();
|
|
74
|
+
const config = getGlobalConfig();
|
|
75
|
+
|
|
76
|
+
if (name) {
|
|
77
|
+
if (debug && !config.seenComponents.has(name)) {
|
|
78
|
+
config.seenComponents.add(name);
|
|
79
|
+
console.log(`[Sidekick] Seen component: ${name}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (config.getReplacement) {
|
|
83
|
+
const replacement = config.getReplacement(name);
|
|
84
|
+
if (replacement) {
|
|
85
|
+
if (debug) console.log(`[Sidekick] Replacing: ${name}`);
|
|
86
|
+
return replacement;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (config.getWrapper) {
|
|
91
|
+
const wrapper = config.getWrapper(name);
|
|
92
|
+
if (wrapper) {
|
|
93
|
+
if (debug) console.log(`[Sidekick] Wrapping: ${name}`);
|
|
94
|
+
return wrapper(type);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return type;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Wrapped jsx function - uses React.createElement to avoid circular alias
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
+
export function jsx(type: any, props: any, key?: string) {
|
|
106
|
+
const { children, ...restProps } = props || {};
|
|
107
|
+
const finalProps = key !== undefined ? { ...restProps, key } : restProps;
|
|
108
|
+
return React.createElement(interceptType(type), finalProps, children);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Wrapped jsxs function (for multiple children)
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
export function jsxs(type: any, props: any, key?: string) {
|
|
114
|
+
const { children, ...restProps } = props || {};
|
|
115
|
+
const finalProps = key !== undefined ? { ...restProps, key } : restProps;
|
|
116
|
+
// For jsxs, children is always an array
|
|
117
|
+
return React.createElement(interceptType(type), finalProps, ...(children || []));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Re-export Fragment from React
|
|
121
|
+
export const Fragment = React.Fragment;
|
|
122
|
+
|
|
123
|
+
// Configuration function - called by SidekickProvider
|
|
124
|
+
export function configureJsxRuntime(
|
|
125
|
+
wrapperGetter: WrapperGetter,
|
|
126
|
+
replacementGetter: ReplacementGetter
|
|
127
|
+
) {
|
|
128
|
+
const config = getGlobalConfig();
|
|
129
|
+
config.getWrapper = wrapperGetter;
|
|
130
|
+
config.getReplacement = replacementGetter;
|
|
131
|
+
if (getDebug()) console.log('[Sidekick] JSX runtime configured (global)');
|
|
132
|
+
|
|
133
|
+
// Notify all listeners that config has changed
|
|
134
|
+
config.listeners.forEach(listener => {
|
|
135
|
+
try {
|
|
136
|
+
listener();
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.error('[Sidekick] Config change listener error:', e);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Subscribe to config changes (for triggering re-renders)
|
|
144
|
+
export function onConfigChange(listener: ConfigChangeListener): () => void {
|
|
145
|
+
const config = getGlobalConfig();
|
|
146
|
+
config.listeners.add(listener);
|
|
147
|
+
return () => config.listeners.delete(listener);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check if the runtime is configured
|
|
151
|
+
export function isJsxRuntimeConfigured(): boolean {
|
|
152
|
+
const config = getGlobalConfig();
|
|
153
|
+
return config.getWrapper !== null || config.getReplacement !== null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getSeenComponentsFromJsx(): string[] {
|
|
157
|
+
const config = getGlobalConfig();
|
|
158
|
+
return Array.from(config.seenComponents);
|
|
159
|
+
}
|
package/src/loader.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { OverrideModule, OverrideManifest } from './types';
|
|
2
|
+
|
|
3
|
+
export interface LoadedOverride {
|
|
4
|
+
manifest: OverrideManifest;
|
|
5
|
+
module: OverrideModule;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// In a real implementation, this would dynamically import from the /sidekick/overrides directory
|
|
9
|
+
// For now, we'll use a registry pattern where overrides register themselves
|
|
10
|
+
|
|
11
|
+
const overrideRegistry = new Map<string, OverrideModule>();
|
|
12
|
+
|
|
13
|
+
export function registerOverride(module: OverrideModule): void {
|
|
14
|
+
overrideRegistry.set(module.manifest.id, module);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getRegisteredOverrides(): OverrideModule[] {
|
|
18
|
+
return Array.from(overrideRegistry.values());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getOverrideById(id: string): OverrideModule | undefined {
|
|
22
|
+
return overrideRegistry.get(id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function clearOverrides(): void {
|
|
26
|
+
overrideRegistry.clear();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Helper to create an override module
|
|
30
|
+
export function createOverride(
|
|
31
|
+
manifest: OverrideManifest,
|
|
32
|
+
activate: OverrideModule['activate']
|
|
33
|
+
): OverrideModule {
|
|
34
|
+
return { manifest, activate };
|
|
35
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BehaviorPrimitives,
|
|
3
|
+
SidekickState,
|
|
4
|
+
EventHandler,
|
|
5
|
+
KeyboardShortcut,
|
|
6
|
+
RouteModifier,
|
|
7
|
+
DOMEventListener,
|
|
8
|
+
} from '../types';
|
|
9
|
+
|
|
10
|
+
function generateId(): string {
|
|
11
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
12
|
+
const r = (Math.random() * 16) | 0;
|
|
13
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
14
|
+
return v.toString(16);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createBehaviorPrimitives(
|
|
19
|
+
state: SidekickState,
|
|
20
|
+
currentOverrideId: string
|
|
21
|
+
): BehaviorPrimitives {
|
|
22
|
+
return {
|
|
23
|
+
onEvent(eventName, handler, options = {}) {
|
|
24
|
+
const eventHandler: EventHandler = {
|
|
25
|
+
id: generateId(),
|
|
26
|
+
overrideId: currentOverrideId,
|
|
27
|
+
eventName,
|
|
28
|
+
handler,
|
|
29
|
+
priority: options.priority ?? 0,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const existing = state.eventHandlers.get(eventName) ?? [];
|
|
33
|
+
existing.push(eventHandler);
|
|
34
|
+
existing.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
35
|
+
state.eventHandlers.set(eventName, existing);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
addKeyboardShortcut(keys, action, description) {
|
|
39
|
+
const shortcut: KeyboardShortcut = {
|
|
40
|
+
id: generateId(),
|
|
41
|
+
overrideId: currentOverrideId,
|
|
42
|
+
keys,
|
|
43
|
+
action,
|
|
44
|
+
description,
|
|
45
|
+
};
|
|
46
|
+
state.keyboardShortcuts.push(shortcut);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
modifyRoute(pathPattern, handler) {
|
|
50
|
+
const modifier: RouteModifier = {
|
|
51
|
+
id: generateId(),
|
|
52
|
+
overrideId: currentOverrideId,
|
|
53
|
+
pathPattern,
|
|
54
|
+
handler,
|
|
55
|
+
};
|
|
56
|
+
state.routeModifiers.push(modifier);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
onDOMEvent(selector, eventType, handler) {
|
|
60
|
+
const listener: DOMEventListener = {
|
|
61
|
+
id: generateId(),
|
|
62
|
+
overrideId: currentOverrideId,
|
|
63
|
+
selector,
|
|
64
|
+
eventType,
|
|
65
|
+
handler,
|
|
66
|
+
};
|
|
67
|
+
state.domEventListeners.push(listener);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|