kimu-core 0.4.1 → 0.4.2

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 (67) hide show
  1. package/.editorconfig +116 -30
  2. package/.gitattributes +81 -11
  3. package/.github/FUNDING.yml +8 -8
  4. package/.github/kimu-copilot-instructions.md +3779 -3779
  5. package/.github/workflows/deploy-demo.yml +39 -39
  6. package/.nvmrc +1 -0
  7. package/.prettierignore +44 -0
  8. package/.prettierrc +16 -0
  9. package/FUNDING.md +31 -31
  10. package/icon.svg +10 -10
  11. package/package.json +9 -2
  12. package/scripts/minify-css-assets.js +82 -82
  13. package/src/core/index.ts +47 -47
  14. package/src/core/kimu-global-styles.ts +136 -136
  15. package/src/core/kimu-reactive.ts +196 -196
  16. package/src/modules-repository/api-axios/CHANGELOG.md +48 -48
  17. package/src/modules-repository/api-axios/QUICK-REFERENCE.md +178 -178
  18. package/src/modules-repository/api-axios/README.md +304 -304
  19. package/src/modules-repository/api-axios/api-axios-service.ts +355 -355
  20. package/src/modules-repository/api-axios/examples.ts +293 -293
  21. package/src/modules-repository/api-axios/index.ts +19 -19
  22. package/src/modules-repository/api-axios/interfaces.ts +71 -71
  23. package/src/modules-repository/api-axios/module.ts +41 -41
  24. package/src/modules-repository/api-core/CHANGELOG.md +42 -42
  25. package/src/modules-repository/api-core/QUICK-REFERENCE.md +192 -192
  26. package/src/modules-repository/api-core/README.md +435 -435
  27. package/src/modules-repository/api-core/api-core-service.ts +289 -289
  28. package/src/modules-repository/api-core/examples.ts +432 -432
  29. package/src/modules-repository/api-core/index.ts +8 -8
  30. package/src/modules-repository/api-core/interfaces.ts +83 -83
  31. package/src/modules-repository/api-core/module.ts +30 -30
  32. package/src/modules-repository/event-bus/README.md +273 -273
  33. package/src/modules-repository/event-bus/event-bus-service.ts +176 -176
  34. package/src/modules-repository/event-bus/module.ts +30 -30
  35. package/src/modules-repository/notification/README.md +423 -423
  36. package/src/modules-repository/notification/module.ts +30 -30
  37. package/src/modules-repository/notification/notification-service.ts +436 -436
  38. package/src/modules-repository/router/README.it.md +61 -10
  39. package/src/modules-repository/router/README.md +61 -10
  40. package/src/modules-repository/router/router-config.ts.example +61 -0
  41. package/src/modules-repository/router/router.ts +18 -0
  42. package/src/modules-repository/state/README.md +409 -409
  43. package/src/modules-repository/state/module.ts +30 -30
  44. package/src/modules-repository/state/state-service.ts +296 -296
  45. package/src/modules-repository/theme/README.md +311 -267
  46. package/src/modules-repository/theme/module.ts +30 -30
  47. package/src/modules-repository/theme/pre-build.js +40 -40
  48. package/src/modules-repository/theme/theme-service.ts +411 -389
  49. package/src/modules-repository/theme/themes/theme-cherry-blossom.css +78 -78
  50. package/src/modules-repository/theme/themes/theme-cozy.css +111 -111
  51. package/src/modules-repository/theme/themes/theme-cyberpunk.css +150 -150
  52. package/src/modules-repository/theme/themes/theme-dark.css +79 -79
  53. package/src/modules-repository/theme/themes/theme-forest.css +171 -171
  54. package/src/modules-repository/theme/themes/theme-gold.css +100 -100
  55. package/src/modules-repository/theme/themes/theme-high-contrast.css +126 -126
  56. package/src/modules-repository/theme/themes/theme-lava.css +101 -101
  57. package/src/modules-repository/theme/themes/theme-lavender.css +90 -90
  58. package/src/modules-repository/theme/themes/theme-light.css +79 -79
  59. package/src/modules-repository/theme/themes/theme-matrix.css +103 -103
  60. package/src/modules-repository/theme/themes/theme-midnight.css +81 -81
  61. package/src/modules-repository/theme/themes/theme-nord.css +94 -94
  62. package/src/modules-repository/theme/themes/theme-ocean.css +84 -84
  63. package/src/modules-repository/theme/themes/theme-retro80s.css +343 -343
  64. package/src/modules-repository/theme/themes/theme-sunset.css +62 -62
  65. package/src/modules-repository/theme/themes-config-default.json +19 -0
  66. package/src/modules-repository/theme/themes-config.d.ts +27 -27
  67. package/src/modules-repository/theme/{themes-config.json → themes-config.json.example} +223 -213
@@ -1,136 +1,136 @@
1
- /**
2
- * KimuGlobalStyles manages global CSS files that should be injected into all extensions.
3
- * This allows modules (like the theme module) to register additional global styles
4
- * without hardcoding dependencies in the core framework.
5
- *
6
- * Usage:
7
- * - Core always injects 'assets/kimu-style.css' as the base style
8
- * - Modules can register additional global CSS using registerGlobalStyle()
9
- * - All registered styles are automatically injected into every extension's shadow root
10
- */
11
- export class KimuGlobalStyles {
12
- /** List of global CSS files to inject in all extensions */
13
- private static _globalStyles: Array<{ id: string; path: string }> = [
14
- // Default KIMU base styles - always included
15
- { id: 'kimu-style-default', path: 'assets/kimu-style.css' }
16
- ];
17
-
18
- /**
19
- * Register a global CSS file to be injected in all extensions.
20
- * This is useful for modules that want to provide global theming or styling.
21
- *
22
- * @param id - Unique identifier for the style (e.g., 'theme-dark', 'theme-light')
23
- * @param path - Path to the CSS file relative to the base path
24
- *
25
- * @example
26
- * // In a theme module
27
- * KimuGlobalStyles.registerGlobalStyle('theme-active', 'themes/theme-dark.css');
28
- */
29
- static registerGlobalStyle(id: string, path: string): void {
30
- // Check if already registered
31
- const existing = this._globalStyles.find(s => s.id === id);
32
- if (existing) {
33
- // Update path if already registered
34
- existing.path = path;
35
- console.log(`[KimuGlobalStyles] Updated global style: ${id} -> ${path}`);
36
- } else {
37
- // Add new global style
38
- this._globalStyles.push({ id, path });
39
- console.log(`[KimuGlobalStyles] Registered global style: ${id} -> ${path}`);
40
- }
41
- }
42
-
43
- /**
44
- * Unregister a global CSS file.
45
- *
46
- * @param id - Unique identifier of the style to remove
47
- */
48
- static unregisterGlobalStyle(id: string): void {
49
- const index = this._globalStyles.findIndex(s => s.id === id);
50
- if (index !== -1) {
51
- this._globalStyles.splice(index, 1);
52
- console.log(`[KimuGlobalStyles] Unregistered global style: ${id}`);
53
- }
54
- }
55
-
56
- /**
57
- * Get all registered global styles.
58
- * Used internally by KimuComponentElement to inject styles into extensions.
59
- *
60
- * @returns Array of registered global styles
61
- */
62
- static getGlobalStyles(): Array<{ id: string; path: string }> {
63
- return [...this._globalStyles]; // Return a copy to prevent external modifications
64
- }
65
-
66
- /**
67
- * Clear all registered styles (useful for testing or reset scenarios).
68
- * Note: This will also remove the default kimu-style.css.
69
- */
70
- static clearAllStyles(): void {
71
- this._globalStyles = [
72
- { id: 'kimu-style-default', path: 'assets/kimu-style.css' }
73
- ];
74
- console.log('[KimuGlobalStyles] Cleared all global styles, keeping only default');
75
- }
76
-
77
- /**
78
- * Get the number of registered global styles.
79
- */
80
- static getStyleCount(): number {
81
- return this._globalStyles.length;
82
- }
83
-
84
- /**
85
- * Check if a specific style ID is registered.
86
- */
87
- static hasStyle(id: string): boolean {
88
- return this._globalStyles.some(s => s.id === id);
89
- }
90
-
91
- /**
92
- * Update a specific global style in all currently loaded extensions.
93
- * This is useful when a theme changes and needs to be propagated to all extensions.
94
- *
95
- * @param id - Style ID to update (e.g., 'kimu-theme-active')
96
- *
97
- * Note: This method uses KimuEngine.injectStyle() to update styles in all shadow roots.
98
- * It requires KimuEngine to be imported dynamically to avoid circular dependencies.
99
- */
100
- static async updateStyleInAllExtensions(id: string): Promise<void> {
101
- const style = this._globalStyles.find(s => s.id === id);
102
- if (!style) {
103
- console.warn(`[KimuGlobalStyles] Style "${id}" not found, cannot update`);
104
- return;
105
- }
106
-
107
- // Find all custom elements (extensions) in the DOM
108
- const allCustomElements = document.querySelectorAll('*');
109
- const extensions: HTMLElement[] = [];
110
-
111
- allCustomElements.forEach((el) => {
112
- // Check if element is a custom element with shadow root
113
- if (el.tagName.includes('-') && (el as any).shadowRoot) {
114
- extensions.push(el as HTMLElement);
115
- }
116
- });
117
-
118
- if (extensions.length === 0) {
119
- console.log('[KimuGlobalStyles] No extensions found to update');
120
- return;
121
- }
122
-
123
- // Dynamically import KimuEngine to avoid circular dependency
124
- const { KimuEngine } = await import('./kimu-engine');
125
-
126
- // Update style in all extensions
127
- console.log(`[KimuGlobalStyles] Updating style "${id}" in ${extensions.length} extensions`);
128
- for (const ext of extensions) {
129
- try {
130
- await KimuEngine.injectStyle(ext, style.path, style.id);
131
- } catch (error) {
132
- console.warn(`[KimuGlobalStyles] Failed to update style in ${ext.tagName}:`, error);
133
- }
134
- }
135
- }
136
- }
1
+ /**
2
+ * KimuGlobalStyles manages global CSS files that should be injected into all extensions.
3
+ * This allows modules (like the theme module) to register additional global styles
4
+ * without hardcoding dependencies in the core framework.
5
+ *
6
+ * Usage:
7
+ * - Core always injects 'assets/kimu-style.css' as the base style
8
+ * - Modules can register additional global CSS using registerGlobalStyle()
9
+ * - All registered styles are automatically injected into every extension's shadow root
10
+ */
11
+ export class KimuGlobalStyles {
12
+ /** List of global CSS files to inject in all extensions */
13
+ private static _globalStyles: Array<{ id: string; path: string }> = [
14
+ // Default KIMU base styles - always included
15
+ { id: 'kimu-style-default', path: 'assets/kimu-style.css' }
16
+ ];
17
+
18
+ /**
19
+ * Register a global CSS file to be injected in all extensions.
20
+ * This is useful for modules that want to provide global theming or styling.
21
+ *
22
+ * @param id - Unique identifier for the style (e.g., 'theme-dark', 'theme-light')
23
+ * @param path - Path to the CSS file relative to the base path
24
+ *
25
+ * @example
26
+ * // In a theme module
27
+ * KimuGlobalStyles.registerGlobalStyle('theme-active', 'themes/theme-dark.css');
28
+ */
29
+ static registerGlobalStyle(id: string, path: string): void {
30
+ // Check if already registered
31
+ const existing = this._globalStyles.find(s => s.id === id);
32
+ if (existing) {
33
+ // Update path if already registered
34
+ existing.path = path;
35
+ console.log(`[KimuGlobalStyles] Updated global style: ${id} -> ${path}`);
36
+ } else {
37
+ // Add new global style
38
+ this._globalStyles.push({ id, path });
39
+ console.log(`[KimuGlobalStyles] Registered global style: ${id} -> ${path}`);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Unregister a global CSS file.
45
+ *
46
+ * @param id - Unique identifier of the style to remove
47
+ */
48
+ static unregisterGlobalStyle(id: string): void {
49
+ const index = this._globalStyles.findIndex(s => s.id === id);
50
+ if (index !== -1) {
51
+ this._globalStyles.splice(index, 1);
52
+ console.log(`[KimuGlobalStyles] Unregistered global style: ${id}`);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get all registered global styles.
58
+ * Used internally by KimuComponentElement to inject styles into extensions.
59
+ *
60
+ * @returns Array of registered global styles
61
+ */
62
+ static getGlobalStyles(): Array<{ id: string; path: string }> {
63
+ return [...this._globalStyles]; // Return a copy to prevent external modifications
64
+ }
65
+
66
+ /**
67
+ * Clear all registered styles (useful for testing or reset scenarios).
68
+ * Note: This will also remove the default kimu-style.css.
69
+ */
70
+ static clearAllStyles(): void {
71
+ this._globalStyles = [
72
+ { id: 'kimu-style-default', path: 'assets/kimu-style.css' }
73
+ ];
74
+ console.log('[KimuGlobalStyles] Cleared all global styles, keeping only default');
75
+ }
76
+
77
+ /**
78
+ * Get the number of registered global styles.
79
+ */
80
+ static getStyleCount(): number {
81
+ return this._globalStyles.length;
82
+ }
83
+
84
+ /**
85
+ * Check if a specific style ID is registered.
86
+ */
87
+ static hasStyle(id: string): boolean {
88
+ return this._globalStyles.some(s => s.id === id);
89
+ }
90
+
91
+ /**
92
+ * Update a specific global style in all currently loaded extensions.
93
+ * This is useful when a theme changes and needs to be propagated to all extensions.
94
+ *
95
+ * @param id - Style ID to update (e.g., 'kimu-theme-active')
96
+ *
97
+ * Note: This method uses KimuEngine.injectStyle() to update styles in all shadow roots.
98
+ * It requires KimuEngine to be imported dynamically to avoid circular dependencies.
99
+ */
100
+ static async updateStyleInAllExtensions(id: string): Promise<void> {
101
+ const style = this._globalStyles.find(s => s.id === id);
102
+ if (!style) {
103
+ console.warn(`[KimuGlobalStyles] Style "${id}" not found, cannot update`);
104
+ return;
105
+ }
106
+
107
+ // Find all custom elements (extensions) in the DOM
108
+ const allCustomElements = document.querySelectorAll('*');
109
+ const extensions: HTMLElement[] = [];
110
+
111
+ allCustomElements.forEach((el) => {
112
+ // Check if element is a custom element with shadow root
113
+ if (el.tagName.includes('-') && (el as any).shadowRoot) {
114
+ extensions.push(el as HTMLElement);
115
+ }
116
+ });
117
+
118
+ if (extensions.length === 0) {
119
+ console.log('[KimuGlobalStyles] No extensions found to update');
120
+ return;
121
+ }
122
+
123
+ // Dynamically import KimuEngine to avoid circular dependency
124
+ const { KimuEngine } = await import('./kimu-engine');
125
+
126
+ // Update style in all extensions
127
+ console.log(`[KimuGlobalStyles] Updating style "${id}" in ${extensions.length} extensions`);
128
+ for (const ext of extensions) {
129
+ try {
130
+ await KimuEngine.injectStyle(ext, style.path, style.id);
131
+ } catch (error) {
132
+ console.warn(`[KimuGlobalStyles] Failed to update style in ${ext.tagName}:`, error);
133
+ }
134
+ }
135
+ }
136
+ }
@@ -1,196 +1,196 @@
1
- /**
2
- * KIMU Reactive System - Optimized & Minimal
3
- *
4
- * Lightweight reactive property decorator for KIMU components.
5
- * Designed for simplicity, performance, and minimal bundle size.
6
- *
7
- * Philosophy: KIMU is intentionally minimal. We focus on doing the essentials
8
- * perfectly, not everything adequately. For complex reactivity needs, use Vue/React.
9
- *
10
- * Usage Example:
11
- *
12
- * ```typescript
13
- * export class MyComponent extends KimuComponentElement {
14
- * @property({ type: Number })
15
- * counter = 0;
16
- *
17
- * @property({ type: String, reflect: true })
18
- * label = 'Counter';
19
- *
20
- * increment() {
21
- * this.counter++; // Auto re-render!
22
- * }
23
- * }
24
- * ```
25
- */
26
-
27
- /**
28
- * Options for the @property decorator
29
- */
30
- export interface PropertyOptions {
31
- /**
32
- * The type of the property. Used for type conversion.
33
- * Supported types: String, Number, Boolean
34
- */
35
- type?: StringConstructor | NumberConstructor | BooleanConstructor;
36
-
37
- /**
38
- * Whether the property should reflect to an attribute.
39
- * If true, changes to the property will update the corresponding HTML attribute.
40
- */
41
- reflect?: boolean;
42
- }
43
-
44
- /**
45
- * Convert property name to attribute name (camelCase to kebab-case)
46
- */
47
- const toKebab = (str: string): string => str.replace(/([A-Z])/g, '-$1').toLowerCase();
48
-
49
- /**
50
- * Convert value to specified type
51
- * @param value - Value to convert
52
- * @param type - Target type constructor
53
- * @returns Converted value
54
- */
55
- function convert(value: any, type?: any): any {
56
- if (!type) return value;
57
- if (type === Number) return value == null ? 0 : Number(value) || 0;
58
- if (type === Boolean) return value !== null && value !== 'false' && value !== false;
59
- if (type === String) return value == null ? '' : String(value);
60
- return value;
61
- }
62
-
63
- /**
64
- * Reactive property decorator
65
- *
66
- * Transforms a class property into a reactive property that automatically
67
- * triggers re-rendering when its value changes.
68
- *
69
- * @param options - Configuration options for the reactive property
70
- * @returns Property decorator function
71
- *
72
- * @example
73
- * ```typescript
74
- * export class Counter extends KimuComponentElement {
75
- * @property({ type: Number })
76
- * count = 0;
77
- *
78
- * @property({ type: String, reflect: true })
79
- * label = 'Counter';
80
- * }
81
- * ```
82
- */
83
- export function property(options: PropertyOptions = {}) {
84
- return function (target: any, propertyKey: string) {
85
- const privateKey = `__${propertyKey}`;
86
- const { type, reflect = false } = options;
87
- const attrName = toKebab(propertyKey);
88
-
89
- // Store metadata for attribute observation
90
- const ctor = target.constructor;
91
- if (!ctor.__kimu_reactive_props__) {
92
- ctor.__kimu_reactive_props__ = new Map();
93
- ctor.__kimu_attr_to_prop__ = new Map(); // Inverse map for O(1) lookup
94
- }
95
- ctor.__kimu_reactive_props__.set(propertyKey, { type, reflect, attribute: attrName });
96
- ctor.__kimu_attr_to_prop__.set(attrName, { propertyKey, type }); // Store inverse mapping
97
-
98
- // Define the reactive property with optimized getter/setter
99
- Object.defineProperty(target, propertyKey, {
100
- get() {
101
- return this[privateKey];
102
- },
103
- set(value: any) {
104
- const oldValue = this[privateKey];
105
- const newValue = type ? convert(value, type) : value;
106
-
107
- // Early return if value hasn't changed
108
- if (oldValue === newValue) return;
109
-
110
- this[privateKey] = newValue;
111
-
112
- // Reflect to attribute if enabled (optimized conditionals)
113
- if (reflect && this instanceof HTMLElement) {
114
- if (newValue == null || newValue === false) {
115
- this.removeAttribute(attrName);
116
- } else {
117
- this.setAttribute(attrName, type === Boolean ? '' : String(newValue));
118
- }
119
- }
120
-
121
- // Batched re-render using RAF (prevents multiple renders in same frame)
122
- // Call refresh directly - it already handles RAF batching internally
123
- if (typeof this.refresh === 'function') {
124
- this.refresh();
125
- }
126
- },
127
- enumerable: true,
128
- configurable: true
129
- });
130
- };
131
- }
132
-
133
-
134
- /**
135
- * Initialize reactive properties from attributes
136
- *
137
- * This should be called in the connectedCallback of components that use @property
138
- *
139
- * @param component - The component instance
140
- */
141
- export function initReactiveProperties(component: any): void {
142
- const props = component.constructor.__kimu_reactive_props__;
143
- if (!props) return;
144
-
145
- // Initialize properties from attributes
146
- props.forEach((options: any, propertyKey: string) => {
147
- const attrValue = component.getAttribute(options.attribute);
148
- if (attrValue !== null) {
149
- (component as any)[propertyKey] = convert(attrValue, options.type);
150
- }
151
- });
152
- }
153
-
154
- /**
155
- * Get observed attributes for reactive properties
156
- *
157
- * @param componentClass - The component class
158
- * @returns Array of observed attributes
159
- */
160
- export function getObservedAttributes(componentClass: any): string[] {
161
- const props = componentClass.__kimu_reactive_props__;
162
- if (!props) return [];
163
-
164
- return Array.from(props.values())
165
- .map((opts: any) => opts.attribute)
166
- .filter(Boolean);
167
- }
168
-
169
- /**
170
- * Handle attribute changes for reactive properties
171
- *
172
- * This should be called from attributeChangedCallback
173
- *
174
- * @param component - The component instance
175
- * @param name - Attribute name
176
- * @param oldValue - Old attribute value
177
- * @param newValue - New attribute value
178
- */
179
- export function handleAttributeChange(
180
- component: any,
181
- name: string,
182
- _oldValue: string | null,
183
- newValue: string | null
184
- ): void {
185
- const attrMap = component.constructor.__kimu_attr_to_prop__;
186
- if (!attrMap) return;
187
-
188
- // O(1) lookup using inverse map
189
- const propInfo = attrMap.get(name);
190
- if (propInfo) {
191
- component[propInfo.propertyKey] = newValue !== null
192
- ? convert(newValue, propInfo.type)
193
- : null;
194
- }
195
- }
196
-
1
+ /**
2
+ * KIMU Reactive System - Optimized & Minimal
3
+ *
4
+ * Lightweight reactive property decorator for KIMU components.
5
+ * Designed for simplicity, performance, and minimal bundle size.
6
+ *
7
+ * Philosophy: KIMU is intentionally minimal. We focus on doing the essentials
8
+ * perfectly, not everything adequately. For complex reactivity needs, use Vue/React.
9
+ *
10
+ * Usage Example:
11
+ *
12
+ * ```typescript
13
+ * export class MyComponent extends KimuComponentElement {
14
+ * @property({ type: Number })
15
+ * counter = 0;
16
+ *
17
+ * @property({ type: String, reflect: true })
18
+ * label = 'Counter';
19
+ *
20
+ * increment() {
21
+ * this.counter++; // Auto re-render!
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+
27
+ /**
28
+ * Options for the @property decorator
29
+ */
30
+ export interface PropertyOptions {
31
+ /**
32
+ * The type of the property. Used for type conversion.
33
+ * Supported types: String, Number, Boolean
34
+ */
35
+ type?: StringConstructor | NumberConstructor | BooleanConstructor;
36
+
37
+ /**
38
+ * Whether the property should reflect to an attribute.
39
+ * If true, changes to the property will update the corresponding HTML attribute.
40
+ */
41
+ reflect?: boolean;
42
+ }
43
+
44
+ /**
45
+ * Convert property name to attribute name (camelCase to kebab-case)
46
+ */
47
+ const toKebab = (str: string): string => str.replace(/([A-Z])/g, '-$1').toLowerCase();
48
+
49
+ /**
50
+ * Convert value to specified type
51
+ * @param value - Value to convert
52
+ * @param type - Target type constructor
53
+ * @returns Converted value
54
+ */
55
+ function convert(value: any, type?: any): any {
56
+ if (!type) return value;
57
+ if (type === Number) return value == null ? 0 : Number(value) || 0;
58
+ if (type === Boolean) return value !== null && value !== 'false' && value !== false;
59
+ if (type === String) return value == null ? '' : String(value);
60
+ return value;
61
+ }
62
+
63
+ /**
64
+ * Reactive property decorator
65
+ *
66
+ * Transforms a class property into a reactive property that automatically
67
+ * triggers re-rendering when its value changes.
68
+ *
69
+ * @param options - Configuration options for the reactive property
70
+ * @returns Property decorator function
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * export class Counter extends KimuComponentElement {
75
+ * @property({ type: Number })
76
+ * count = 0;
77
+ *
78
+ * @property({ type: String, reflect: true })
79
+ * label = 'Counter';
80
+ * }
81
+ * ```
82
+ */
83
+ export function property(options: PropertyOptions = {}) {
84
+ return function (target: any, propertyKey: string) {
85
+ const privateKey = `__${propertyKey}`;
86
+ const { type, reflect = false } = options;
87
+ const attrName = toKebab(propertyKey);
88
+
89
+ // Store metadata for attribute observation
90
+ const ctor = target.constructor;
91
+ if (!ctor.__kimu_reactive_props__) {
92
+ ctor.__kimu_reactive_props__ = new Map();
93
+ ctor.__kimu_attr_to_prop__ = new Map(); // Inverse map for O(1) lookup
94
+ }
95
+ ctor.__kimu_reactive_props__.set(propertyKey, { type, reflect, attribute: attrName });
96
+ ctor.__kimu_attr_to_prop__.set(attrName, { propertyKey, type }); // Store inverse mapping
97
+
98
+ // Define the reactive property with optimized getter/setter
99
+ Object.defineProperty(target, propertyKey, {
100
+ get() {
101
+ return this[privateKey];
102
+ },
103
+ set(value: any) {
104
+ const oldValue = this[privateKey];
105
+ const newValue = type ? convert(value, type) : value;
106
+
107
+ // Early return if value hasn't changed
108
+ if (oldValue === newValue) return;
109
+
110
+ this[privateKey] = newValue;
111
+
112
+ // Reflect to attribute if enabled (optimized conditionals)
113
+ if (reflect && this instanceof HTMLElement) {
114
+ if (newValue == null || newValue === false) {
115
+ this.removeAttribute(attrName);
116
+ } else {
117
+ this.setAttribute(attrName, type === Boolean ? '' : String(newValue));
118
+ }
119
+ }
120
+
121
+ // Batched re-render using RAF (prevents multiple renders in same frame)
122
+ // Call refresh directly - it already handles RAF batching internally
123
+ if (typeof this.refresh === 'function') {
124
+ this.refresh();
125
+ }
126
+ },
127
+ enumerable: true,
128
+ configurable: true
129
+ });
130
+ };
131
+ }
132
+
133
+
134
+ /**
135
+ * Initialize reactive properties from attributes
136
+ *
137
+ * This should be called in the connectedCallback of components that use @property
138
+ *
139
+ * @param component - The component instance
140
+ */
141
+ export function initReactiveProperties(component: any): void {
142
+ const props = component.constructor.__kimu_reactive_props__;
143
+ if (!props) return;
144
+
145
+ // Initialize properties from attributes
146
+ props.forEach((options: any, propertyKey: string) => {
147
+ const attrValue = component.getAttribute(options.attribute);
148
+ if (attrValue !== null) {
149
+ (component as any)[propertyKey] = convert(attrValue, options.type);
150
+ }
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Get observed attributes for reactive properties
156
+ *
157
+ * @param componentClass - The component class
158
+ * @returns Array of observed attributes
159
+ */
160
+ export function getObservedAttributes(componentClass: any): string[] {
161
+ const props = componentClass.__kimu_reactive_props__;
162
+ if (!props) return [];
163
+
164
+ return Array.from(props.values())
165
+ .map((opts: any) => opts.attribute)
166
+ .filter(Boolean);
167
+ }
168
+
169
+ /**
170
+ * Handle attribute changes for reactive properties
171
+ *
172
+ * This should be called from attributeChangedCallback
173
+ *
174
+ * @param component - The component instance
175
+ * @param name - Attribute name
176
+ * @param oldValue - Old attribute value
177
+ * @param newValue - New attribute value
178
+ */
179
+ export function handleAttributeChange(
180
+ component: any,
181
+ name: string,
182
+ _oldValue: string | null,
183
+ newValue: string | null
184
+ ): void {
185
+ const attrMap = component.constructor.__kimu_attr_to_prop__;
186
+ if (!attrMap) return;
187
+
188
+ // O(1) lookup using inverse map
189
+ const propInfo = attrMap.get(name);
190
+ if (propInfo) {
191
+ component[propInfo.propertyKey] = newValue !== null
192
+ ? convert(newValue, propInfo.type)
193
+ : null;
194
+ }
195
+ }
196
+