@idealyst/components 1.1.6 → 1.1.7

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 (104) hide show
  1. package/package.json +8 -3
  2. package/src/Accordion/Accordion.native.tsx +15 -9
  3. package/src/Accordion/Accordion.styles.tsx +193 -168
  4. package/src/Accordion/Accordion.web.tsx +12 -7
  5. package/src/ActivityIndicator/ActivityIndicator.native.tsx +3 -2
  6. package/src/ActivityIndicator/ActivityIndicator.styles.tsx +22 -11
  7. package/src/ActivityIndicator/ActivityIndicator.web.tsx +2 -2
  8. package/src/Alert/Alert.native.tsx +11 -10
  9. package/src/Alert/Alert.styles.tsx +162 -253
  10. package/src/Alert/Alert.web.tsx +6 -10
  11. package/src/Avatar/Avatar.native.tsx +5 -2
  12. package/src/Avatar/Avatar.styles.tsx +48 -18
  13. package/src/Avatar/Avatar.web.tsx +2 -2
  14. package/src/Badge/Badge.native.tsx +2 -2
  15. package/src/Badge/Badge.styles.tsx +37 -16
  16. package/src/Badge/Badge.web.tsx +6 -6
  17. package/src/Breadcrumb/Breadcrumb.native.tsx +12 -5
  18. package/src/Breadcrumb/Breadcrumb.styles.tsx +59 -58
  19. package/src/Breadcrumb/Breadcrumb.web.tsx +13 -6
  20. package/src/Button/Button.native.tsx +39 -14
  21. package/src/Button/Button.styles.tsx +106 -208
  22. package/src/Button/Button.web.tsx +10 -8
  23. package/src/Card/Card.native.tsx +14 -6
  24. package/src/Card/Card.styles.tsx +64 -62
  25. package/src/Card/Card.web.tsx +5 -4
  26. package/src/Checkbox/Checkbox.native.tsx +7 -3
  27. package/src/Checkbox/Checkbox.styles.tsx +49 -25
  28. package/src/Checkbox/Checkbox.web.tsx +3 -3
  29. package/src/Chip/Chip.native.tsx +5 -5
  30. package/src/Chip/Chip.styles.tsx +71 -21
  31. package/src/Chip/Chip.web.tsx +5 -5
  32. package/src/Dialog/Dialog.native.tsx +10 -4
  33. package/src/Dialog/Dialog.styles.tsx +130 -90
  34. package/src/Dialog/Dialog.web.tsx +4 -4
  35. package/src/Divider/Divider.native.tsx +29 -42
  36. package/src/Divider/Divider.styles.tsx +138 -242
  37. package/src/Divider/Divider.web.tsx +17 -14
  38. package/src/Icon/Icon.native.tsx +11 -3
  39. package/src/Icon/Icon.styles.tsx +10 -4
  40. package/src/Image/Image.styles.tsx +53 -37
  41. package/src/Input/Input.native.tsx +6 -7
  42. package/src/Input/Input.styles.tsx +194 -174
  43. package/src/Input/Input.web.tsx +5 -8
  44. package/src/Link/Link.native.tsx +4 -1
  45. package/src/List/List.styles.tsx +79 -105
  46. package/src/List/ListItem.native.tsx +5 -3
  47. package/src/List/ListItem.web.tsx +4 -3
  48. package/src/Menu/Menu.native.tsx +1 -1
  49. package/src/Menu/Menu.styles.tsx +53 -37
  50. package/src/Menu/Menu.web.tsx +2 -2
  51. package/src/Menu/MenuItem.native.tsx +5 -3
  52. package/src/Menu/MenuItem.styles.tsx +68 -69
  53. package/src/Menu/MenuItem.web.tsx +16 -3
  54. package/src/Popover/Popover.native.tsx +1 -1
  55. package/src/Popover/Popover.styles.tsx +40 -29
  56. package/src/Popover/Popover.web.tsx +1 -1
  57. package/src/Pressable/Pressable.native.tsx +3 -1
  58. package/src/Pressable/Pressable.styles.tsx +20 -13
  59. package/src/Pressable/Pressable.web.tsx +1 -1
  60. package/src/Progress/Progress.native.tsx +15 -6
  61. package/src/Progress/Progress.styles.tsx +125 -85
  62. package/src/Progress/Progress.web.tsx +10 -9
  63. package/src/RadioButton/RadioButton.native.tsx +8 -3
  64. package/src/RadioButton/RadioButton.styles.tsx +44 -37
  65. package/src/RadioButton/RadioButton.web.tsx +3 -3
  66. package/src/SVGImage/SVGImage.styles.tsx +28 -16
  67. package/src/Screen/Screen.native.tsx +23 -13
  68. package/src/Screen/Screen.styles.tsx +57 -46
  69. package/src/Screen/Screen.web.tsx +1 -1
  70. package/src/Select/Select.native.tsx +11 -5
  71. package/src/Select/Select.styles.tsx +72 -52
  72. package/src/Select/Select.web.tsx +5 -5
  73. package/src/Skeleton/Skeleton.styles.tsx +26 -14
  74. package/src/Slider/Slider.native.tsx +9 -5
  75. package/src/Slider/Slider.styles.tsx +59 -48
  76. package/src/Slider/Slider.web.tsx +5 -5
  77. package/src/Switch/Switch.native.tsx +6 -2
  78. package/src/Switch/Switch.styles.tsx +46 -19
  79. package/src/Switch/Switch.web.tsx +4 -4
  80. package/src/TabBar/TabBar.native.tsx +23 -31
  81. package/src/TabBar/TabBar.styles.tsx +215 -371
  82. package/src/TabBar/TabBar.web.tsx +21 -33
  83. package/src/Table/Table.native.tsx +1 -1
  84. package/src/Table/Table.styles.tsx +11 -4
  85. package/src/Table/Table.web.tsx +1 -1
  86. package/src/Text/Text.native.tsx +3 -4
  87. package/src/Text/Text.styles.tsx +7 -1
  88. package/src/Text/Text.web.tsx +1 -1
  89. package/src/TextArea/TextArea.styles.tsx +90 -58
  90. package/src/Tooltip/Tooltip.native.tsx +2 -2
  91. package/src/Tooltip/Tooltip.styles.tsx +21 -12
  92. package/src/Tooltip/Tooltip.web.tsx +2 -2
  93. package/src/Video/Video.styles.tsx +39 -23
  94. package/src/View/View.native.tsx +4 -2
  95. package/src/View/View.styles.tsx +33 -22
  96. package/src/View/View.web.tsx +13 -2
  97. package/src/extensions/applyExtension.ts +210 -0
  98. package/src/extensions/extendComponent.ts +377 -0
  99. package/src/extensions/index.ts +102 -0
  100. package/src/extensions/types.ts +497 -0
  101. package/src/globals.ts +16 -0
  102. package/src/index.native.ts +4 -0
  103. package/src/index.ts +28 -0
  104. package/src/utils/deepMerge.ts +54 -2
@@ -1,4 +1,5 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { forwardRef, useMemo } from 'react';
2
+ import { StyleSheet } from 'react-native';
2
3
  import { getWebProps } from 'react-native-unistyles/web';
3
4
  import { ViewProps } from './types';
4
5
  import { viewStyles } from './View.styles';
@@ -48,13 +49,23 @@ const View = forwardRef<HTMLDivElement, ViewProps>(({
48
49
  if (borderWidth !== undefined) dynamicStyles.borderWidth = borderWidth;
49
50
  if (borderColor) dynamicStyles.borderColor = borderColor;
50
51
 
52
+ // Flatten style array to object (HTML divs don't support style arrays)
53
+ const flattenedStyle = useMemo(() => {
54
+ if (!style) return undefined;
55
+ if (Array.isArray(style)) {
56
+ return StyleSheet.flatten(style);
57
+ }
58
+ return style;
59
+ }, [style]);
60
+
51
61
  /** @ts-ignore */
52
- const webProps = getWebProps([viewStyles.view, dynamicStyles, style as any]);
62
+ const webProps = getWebProps([(viewStyles.view as any)({}), dynamicStyles]);
53
63
 
54
64
  const mergedRef = useMergeRefs(ref, webProps.ref);
55
65
 
56
66
  return (
57
67
  <div
68
+ style={flattenedStyle as any}
58
69
  {...webProps}
59
70
  ref={mergedRef}
60
71
  id={id}
@@ -0,0 +1,210 @@
1
+ import { deepMerge } from '../utils/deepMerge';
2
+ import { Styles, ElementStyle, ComponentName } from './types';
3
+ import { getExtension, getReplacement } from './extendComponent';
4
+ import { Theme } from '@idealyst/theme';
5
+
6
+ /**
7
+ * Wrap a dynamic style function to merge with extension styles.
8
+ *
9
+ * All styles in Unistyles must be dynamic functions (not static objects)
10
+ * to avoid Babel transform issues. This utility wraps a style function
11
+ * to automatically merge extension styles when the function is called.
12
+ *
13
+ * @param styleFn - The original dynamic style function
14
+ * @param elementExtension - Extension styles for this element (can be undefined).
15
+ * Can be either a static styles object or a function (props) => styles
16
+ * for prop-aware extensions.
17
+ * @returns A new function that returns merged styles
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { withExtension } from '../extensions/applyExtension';
22
+ * import { getExtension } from '../extensions/extendComponent';
23
+ *
24
+ * export const buttonStyles = StyleSheet.create((theme: Theme) => {
25
+ * const ext = getExtension('Button', theme);
26
+ *
27
+ * return {
28
+ * button: withExtension(createButtonStyles(theme), ext?.button),
29
+ * text: withExtension(createTextStyles(theme), ext?.text),
30
+ * };
31
+ * });
32
+ * ```
33
+ *
34
+ * @remarks
35
+ * - If no extension is provided, returns the original function unchanged
36
+ * - Extension styles take priority over base styles (deep merged)
37
+ * - Works with any style function signature
38
+ * - If extension is a function, it receives the same props as the base style function
39
+ */
40
+ export function withExtension<TProps, TResult extends Styles>(
41
+ styleFn: (props: TProps) => TResult,
42
+ elementExtension: Styles | ((props: TProps) => Styles) | undefined
43
+ ): (props: TProps) => TResult {
44
+ // If no extension, return original function unchanged
45
+ if (!elementExtension) {
46
+ return styleFn;
47
+ }
48
+
49
+ // Return wrapped function that merges extension
50
+ return (props: TProps): TResult => {
51
+ const baseStyles = styleFn(props);
52
+ // If extension is a function, call it with props; otherwise use as-is
53
+ const extStyles = typeof elementExtension === 'function'
54
+ ? elementExtension(props)
55
+ : elementExtension;
56
+ return deepMerge(baseStyles, extStyles) as TResult;
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Wrap a parameterless style function with extension.
62
+ *
63
+ * Use this for style functions that don't take any parameters.
64
+ * This is common for simpler elements like iconContainer.
65
+ *
66
+ * @param styleFn - The original style function (no parameters)
67
+ * @param elementExtension - Extension styles for this element
68
+ * @returns A new function that returns merged styles
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const createIconContainerStyles = (theme: Theme) => {
73
+ * return () => ({
74
+ * display: 'flex',
75
+ * flexDirection: 'row',
76
+ * gap: 4,
77
+ * });
78
+ * };
79
+ *
80
+ * // In StyleSheet.create:
81
+ * iconContainer: withSimpleExtension(
82
+ * createIconContainerStyles(theme),
83
+ * ext?.iconContainer
84
+ * ),
85
+ * ```
86
+ */
87
+ export function withSimpleExtension<TResult extends Styles>(
88
+ styleFn: () => TResult,
89
+ elementExtension: Styles | undefined
90
+ ): () => TResult {
91
+ if (!elementExtension) {
92
+ return styleFn;
93
+ }
94
+
95
+ return (): TResult => {
96
+ const baseStyles = styleFn();
97
+ return deepMerge(baseStyles, elementExtension) as TResult;
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Normalize a style value (from replacement) into a dynamic function.
103
+ *
104
+ * Replacements can be either:
105
+ * - A function (props) => styles - used directly
106
+ * - A static styles object - wrapped in a function
107
+ *
108
+ * @param value - The replacement value (function or static object)
109
+ * @param defaultFn - Default function to use if value is undefined
110
+ * @returns The default function (type-safe) - replacement handling is done at runtime
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const replacement = getReplacement('Button', theme);
115
+ *
116
+ * // If replacement.button is a function, use it directly
117
+ * // If replacement.button is an object, wrap it in () => replacement.button
118
+ * // If undefined, use createButtonStyles(theme)
119
+ * button: withExtension(
120
+ * normalizeStyleFn(replacement?.button, createButtonStyles(theme)),
121
+ * ext?.button
122
+ * ),
123
+ * ```
124
+ */
125
+ export function normalizeStyleFn<TProps, TResult>(
126
+ value: unknown,
127
+ defaultFn: (props: TProps) => TResult
128
+ ): (props: TProps) => TResult {
129
+ if (value === undefined || value === null) {
130
+ return defaultFn;
131
+ }
132
+ if (typeof value === 'function') {
133
+ return value as (props: TProps) => TResult;
134
+ }
135
+ // Static object - wrap in a function that ignores props
136
+ return (() => value) as (props: TProps) => TResult;
137
+ }
138
+
139
+ /**
140
+ * Normalize a simple style value (no props) into a parameterless function.
141
+ *
142
+ * @param value - The replacement value (function or static object)
143
+ * @param defaultFn - Default function to use if value is undefined
144
+ * @returns A parameterless function that returns styles
145
+ */
146
+ export function normalizeSimpleStyleFn<TResult>(
147
+ value: unknown,
148
+ defaultFn: () => TResult
149
+ ): () => TResult {
150
+ if (value === undefined || value === null) {
151
+ return defaultFn;
152
+ }
153
+ if (typeof value === 'function') {
154
+ return value as () => TResult;
155
+ }
156
+ // Static object - wrap in a function
157
+ return (() => value) as () => TResult;
158
+ }
159
+
160
+ /**
161
+ * Apply extensions and replacements to a set of style creators.
162
+ *
163
+ * This is a simplified helper that handles the common pattern of:
164
+ * 1. Getting extensions and replacements for a component
165
+ * 2. Applying normalizeStyleFn for each element
166
+ * 3. Merging extensions on top
167
+ *
168
+ * @param component - The component name
169
+ * @param theme - The current theme
170
+ * @param styleCreators - Object mapping element names to their style creator functions
171
+ * @returns Object with the same keys, but with extensions/replacements applied
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * export const buttonStyles = StyleSheet.create((theme: Theme) => {
176
+ * return applyExtensions('Button', theme, {
177
+ * button: createButtonStyles(theme),
178
+ * text: createTextStyles(theme),
179
+ * icon: createIconStyles(theme),
180
+ * });
181
+ * });
182
+ * ```
183
+ */
184
+ export function applyExtensions<
185
+ K extends ComponentName,
186
+ T extends Record<string, ((...args: any[]) => any)>
187
+ >(
188
+ component: K,
189
+ theme: Theme,
190
+ styleCreators: T
191
+ ): T {
192
+ const ext = getExtension(component, theme);
193
+ const replacement = getReplacement(component, theme);
194
+
195
+ const result = {} as T;
196
+
197
+ for (const key in styleCreators) {
198
+ const creator = styleCreators[key];
199
+ const elementExt = ext?.[key as string as keyof typeof ext] as ElementStyle | undefined;
200
+ const elementReplacement = replacement?.[key as string as keyof typeof replacement];
201
+
202
+ // Apply replacement (if any) then extension (if any)
203
+ result[key] = withExtension(
204
+ normalizeStyleFn(elementReplacement, creator),
205
+ elementExt
206
+ ) as T[typeof key];
207
+ }
208
+
209
+ return result;
210
+ }
@@ -0,0 +1,377 @@
1
+ import { Theme } from '@idealyst/theme';
2
+ import {
3
+ ComponentStyleElements,
4
+ ComponentName,
5
+ StyleExtension,
6
+ } from './types';
7
+ import { deepMergeAll } from '../utils/deepMerge';
8
+
9
+ /**
10
+ * Registry storing style extensions for each component.
11
+ * Key is the component name, value is an array of extensions (applied in order).
12
+ */
13
+ const extensionRegistry = new Map<ComponentName, StyleExtension<any>[]>();
14
+
15
+ /**
16
+ * Registry storing complete style replacements for each component.
17
+ * When set, the replacement is used instead of base styles + extensions.
18
+ */
19
+ const replacementRegistry = new Map<ComponentName, StyleExtension<any>>();
20
+
21
+ /**
22
+ * Completely replace the styles of a component.
23
+ *
24
+ * Unlike `extendComponent` which merges with base styles, `replaceStyles`
25
+ * completely overrides the default stylesheet. Extensions are NOT applied
26
+ * when a replacement is set.
27
+ *
28
+ * **Use with caution:** You're responsible for providing all necessary styles,
29
+ * including variant styles, platform-specific styles, and accessibility states.
30
+ *
31
+ * @param component - The component name to replace styles for
32
+ * @param replacement - Complete style replacement, either as an object or a function receiving theme
33
+ *
34
+ * @example Complete replacement with theme access
35
+ * ```typescript
36
+ * import { replaceStyles } from '@idealyst/components';
37
+ *
38
+ * replaceStyles('Button', (theme) => ({
39
+ * button: {
40
+ * backgroundColor: theme.colors.surface.primary,
41
+ * borderRadius: 0,
42
+ * padding: 16,
43
+ * variants: {
44
+ * size: {
45
+ * sm: { padding: 8 },
46
+ * md: { padding: 16 },
47
+ * lg: { padding: 24 },
48
+ * },
49
+ * },
50
+ * },
51
+ * text: {
52
+ * color: theme.colors.text.primary,
53
+ * fontSize: 16,
54
+ * },
55
+ * icon: {
56
+ * width: 20,
57
+ * height: 20,
58
+ * },
59
+ * iconContainer: {
60
+ * display: 'flex',
61
+ * alignItems: 'center',
62
+ * },
63
+ * }));
64
+ * ```
65
+ *
66
+ * @example Clear replacement to restore defaults
67
+ * ```typescript
68
+ * clearReplacement('Button');
69
+ * ```
70
+ */
71
+ export function replaceStyles<K extends ComponentName>(
72
+ component: K,
73
+ replacement: StyleExtension<ComponentStyleElements[K]>
74
+ ): void {
75
+ replacementRegistry.set(component, replacement);
76
+ }
77
+
78
+ /**
79
+ * Get the replacement styles for a component, if set.
80
+ *
81
+ * @param component - The component name
82
+ * @param theme - The current theme (used if replacement is a function)
83
+ * @returns The resolved replacement styles, or undefined if none set
84
+ *
85
+ * @internal
86
+ */
87
+ export function getReplacement<K extends ComponentName>(
88
+ component: K,
89
+ theme: Theme
90
+ ): Partial<ComponentStyleElements[K]> | undefined {
91
+ const replacement = replacementRegistry.get(component);
92
+ if (!replacement) {
93
+ return undefined;
94
+ }
95
+ return typeof replacement === 'function' ? replacement(theme) : replacement;
96
+ }
97
+
98
+ /**
99
+ * Clear the style replacement for a specific component.
100
+ * After clearing, the component will use base styles + extensions again.
101
+ *
102
+ * @param component - The component name to clear replacement for
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * import { clearReplacement } from '@idealyst/components';
107
+ *
108
+ * clearReplacement('Button');
109
+ * ```
110
+ */
111
+ export function clearReplacement<K extends ComponentName>(component: K): void {
112
+ replacementRegistry.delete(component);
113
+ }
114
+
115
+ /**
116
+ * Clear all style replacements.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * import { clearAllReplacements } from '@idealyst/components';
121
+ *
122
+ * clearAllReplacements();
123
+ * ```
124
+ */
125
+ export function clearAllReplacements(): void {
126
+ replacementRegistry.clear();
127
+ }
128
+
129
+ /**
130
+ * Check if a component has a style replacement set.
131
+ *
132
+ * @param component - The component name to check
133
+ * @returns true if the component has a replacement set
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * import { hasReplacement } from '@idealyst/components';
138
+ *
139
+ * if (hasReplacement('Button')) {
140
+ * console.log('Button styles are completely replaced');
141
+ * }
142
+ * ```
143
+ */
144
+ export function hasReplacement<K extends ComponentName>(component: K): boolean {
145
+ return replacementRegistry.has(component);
146
+ }
147
+
148
+ /**
149
+ * Globally extend the styles of a component.
150
+ *
151
+ * Extensions affect ALL instances of that component app-wide.
152
+ * Extensions are merged with base styles - extension styles win on conflict.
153
+ *
154
+ * **Precedence rules:**
155
+ * - Extensions override base component styles
156
+ * - Later extensions override earlier extensions
157
+ * - Setting a value to `undefined` removes that style property
158
+ * - Nested objects (like `_web`, `variants`) are deep merged
159
+ *
160
+ * @param component - The component name to extend (e.g., 'Button', 'Card')
161
+ * @param extension - Style overrides, either as an object or a function receiving theme
162
+ *
163
+ * @example Static extension
164
+ * ```typescript
165
+ * import { extendComponent } from '@idealyst/components';
166
+ *
167
+ * extendComponent('Button', {
168
+ * button: {
169
+ * borderRadius: 20,
170
+ * shadowColor: '#000',
171
+ * shadowOffset: { width: 0, height: 2 },
172
+ * shadowOpacity: 0.25,
173
+ * shadowRadius: 4,
174
+ * },
175
+ * text: {
176
+ * textTransform: 'uppercase',
177
+ * letterSpacing: 1,
178
+ * },
179
+ * });
180
+ * ```
181
+ *
182
+ * @example Theme-aware extension
183
+ * ```typescript
184
+ * extendComponent('Button', (theme) => ({
185
+ * button: {
186
+ * ...theme.shadows.lg,
187
+ * backgroundColor: theme.colors.surface.secondary,
188
+ * },
189
+ * text: {
190
+ * color: theme.colors.text.primary,
191
+ * },
192
+ * }));
193
+ * ```
194
+ *
195
+ * @example Multiple extensions (later ones have higher precedence)
196
+ * ```typescript
197
+ * // Base brand styles
198
+ * extendComponent('Button', {
199
+ * button: { borderRadius: 8 },
200
+ * });
201
+ *
202
+ * // Feature-specific override (wins over base)
203
+ * extendComponent('Button', {
204
+ * button: { borderRadius: 20 },
205
+ * });
206
+ * // Result: borderRadius is 20
207
+ * ```
208
+ *
209
+ * @example Removing a style property
210
+ * ```typescript
211
+ * extendComponent('Button', {
212
+ * button: {
213
+ * shadowColor: undefined, // Removes shadowColor
214
+ * shadowOpacity: undefined, // Removes shadowOpacity
215
+ * },
216
+ * });
217
+ * ```
218
+ *
219
+ * @example Web-specific styles
220
+ * ```typescript
221
+ * extendComponent('Button', {
222
+ * button: {
223
+ * _web: {
224
+ * cursor: 'pointer',
225
+ * transition: 'all 0.2s ease',
226
+ * _hover: {
227
+ * transform: 'scale(1.02)',
228
+ * },
229
+ * },
230
+ * },
231
+ * });
232
+ * ```
233
+ */
234
+ // Overload for static object extension
235
+ export function extendComponent<K extends ComponentName>(
236
+ component: K,
237
+ extension: Partial<ComponentStyleElements[K]>
238
+ ): void;
239
+ // Overload for theme-aware function extension
240
+ export function extendComponent<K extends ComponentName>(
241
+ component: K,
242
+ extension: (theme: Theme) => Partial<ComponentStyleElements[K]>
243
+ ): void;
244
+ // Implementation
245
+ export function extendComponent<K extends ComponentName>(
246
+ component: K,
247
+ extension: StyleExtension<ComponentStyleElements[K]>
248
+ ): void {
249
+ const existing = extensionRegistry.get(component) ?? [];
250
+ existing.push(extension);
251
+ extensionRegistry.set(component, existing);
252
+ }
253
+
254
+ /**
255
+ * Get the resolved extension for a component.
256
+ *
257
+ * This is an internal function used by component stylesheets to retrieve
258
+ * and apply extensions. All registered extensions are merged in order,
259
+ * with later extensions taking precedence.
260
+ *
261
+ * @param component - The component name
262
+ * @param theme - The current theme (used if extension is a function)
263
+ * @returns The resolved merged extension styles, or undefined if none registered
264
+ *
265
+ * @internal
266
+ */
267
+ export function getExtension<K extends ComponentName>(
268
+ component: K,
269
+ theme: Theme
270
+ ): Partial<ComponentStyleElements[K]> | undefined {
271
+ const extensions = extensionRegistry.get(component);
272
+
273
+ if (!extensions || extensions.length === 0) {
274
+ return undefined;
275
+ }
276
+
277
+ // Resolve all extensions (call functions with theme)
278
+ const resolved = extensions.map(ext =>
279
+ typeof ext === 'function' ? ext(theme) : ext
280
+ );
281
+
282
+ // Merge all extensions in order (later ones win)
283
+ return deepMergeAll(...resolved) as Partial<ComponentStyleElements[K]>;
284
+ }
285
+
286
+ /**
287
+ * Clear all extensions for a specific component.
288
+ *
289
+ * @param component - The component name to clear extensions for
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * import { clearExtension } from '@idealyst/components';
294
+ *
295
+ * // Remove all Button extensions
296
+ * clearExtension('Button');
297
+ * ```
298
+ */
299
+ export function clearExtension<K extends ComponentName>(component: K): void {
300
+ extensionRegistry.delete(component);
301
+ }
302
+
303
+ /**
304
+ * Clear all component extensions.
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * import { clearAllExtensions } from '@idealyst/components';
309
+ *
310
+ * // Remove all component extensions
311
+ * clearAllExtensions();
312
+ * ```
313
+ */
314
+ export function clearAllExtensions(): void {
315
+ extensionRegistry.clear();
316
+ }
317
+
318
+ /**
319
+ * Check if a component has any extensions registered.
320
+ *
321
+ * @param component - The component name to check
322
+ * @returns true if the component has at least one extension
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * import { hasExtension } from '@idealyst/components';
327
+ *
328
+ * if (hasExtension('Button')) {
329
+ * console.log('Button has custom styles');
330
+ * }
331
+ * ```
332
+ */
333
+ export function hasExtension<K extends ComponentName>(component: K): boolean {
334
+ const extensions = extensionRegistry.get(component);
335
+ return extensions !== undefined && extensions.length > 0;
336
+ }
337
+
338
+ /**
339
+ * Get all registered component extensions.
340
+ * Useful for debugging or inspecting what extensions are active.
341
+ *
342
+ * @returns Array of component names that have extensions
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * import { getExtendedComponents } from '@idealyst/components';
347
+ *
348
+ * const extended = getExtendedComponents();
349
+ * console.log('Extended components:', extended);
350
+ * // ['Button', 'Card', 'Input']
351
+ * ```
352
+ */
353
+ export function getExtendedComponents(): ComponentName[] {
354
+ return Array.from(extensionRegistry.keys()).filter(key => {
355
+ const extensions = extensionRegistry.get(key);
356
+ return extensions && extensions.length > 0;
357
+ });
358
+ }
359
+
360
+ /**
361
+ * Get the number of extensions registered for a component.
362
+ * Useful for debugging.
363
+ *
364
+ * @param component - The component name
365
+ * @returns Number of extensions registered
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * import { getExtensionCount } from '@idealyst/components';
370
+ *
371
+ * console.log('Button extensions:', getExtensionCount('Button'));
372
+ * // 3
373
+ * ```
374
+ */
375
+ export function getExtensionCount<K extends ComponentName>(component: K): number {
376
+ return extensionRegistry.get(component)?.length ?? 0;
377
+ }