@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.
Files changed (48) hide show
  1. package/README.md +246 -0
  2. package/dist/index.d.mts +358 -0
  3. package/dist/index.d.ts +358 -0
  4. package/dist/index.js +2470 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +2403 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/jsx-dev-runtime.d.mts +21 -0
  9. package/dist/jsx-dev-runtime.d.ts +21 -0
  10. package/dist/jsx-dev-runtime.js +160 -0
  11. package/dist/jsx-dev-runtime.js.map +1 -0
  12. package/dist/jsx-dev-runtime.mjs +122 -0
  13. package/dist/jsx-dev-runtime.mjs.map +1 -0
  14. package/dist/jsx-runtime.d.mts +26 -0
  15. package/dist/jsx-runtime.d.ts +26 -0
  16. package/dist/jsx-runtime.js +150 -0
  17. package/dist/jsx-runtime.js.map +1 -0
  18. package/dist/jsx-runtime.mjs +109 -0
  19. package/dist/jsx-runtime.mjs.map +1 -0
  20. package/dist/server/index.d.mts +235 -0
  21. package/dist/server/index.d.ts +235 -0
  22. package/dist/server/index.js +642 -0
  23. package/dist/server/index.js.map +1 -0
  24. package/dist/server/index.mjs +597 -0
  25. package/dist/server/index.mjs.map +1 -0
  26. package/package.json +64 -0
  27. package/src/components/SidekickPanel.tsx +868 -0
  28. package/src/components/index.ts +1 -0
  29. package/src/context.tsx +157 -0
  30. package/src/flags.ts +47 -0
  31. package/src/index.ts +71 -0
  32. package/src/jsx-dev-runtime.ts +138 -0
  33. package/src/jsx-runtime.ts +159 -0
  34. package/src/loader.ts +35 -0
  35. package/src/primitives/behavior.ts +70 -0
  36. package/src/primitives/data.ts +91 -0
  37. package/src/primitives/index.ts +3 -0
  38. package/src/primitives/ui.ts +268 -0
  39. package/src/provider.tsx +1264 -0
  40. package/src/runtime-loader.ts +106 -0
  41. package/src/server/drizzle-adapter.ts +53 -0
  42. package/src/server/drizzle-schema.ts +16 -0
  43. package/src/server/generate.ts +578 -0
  44. package/src/server/handler.ts +343 -0
  45. package/src/server/index.ts +20 -0
  46. package/src/server/storage.ts +1 -0
  47. package/src/server/types.ts +49 -0
  48. package/src/types.ts +295 -0
@@ -0,0 +1 @@
1
+ export { SidekickPanel } from './SidekickPanel';
@@ -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
+ }