fynixui 1.0.10

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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/dist/context/context.d.ts +19 -0
  3. package/dist/context/context.d.ts.map +1 -0
  4. package/dist/context/context.js +4 -0
  5. package/dist/context/context.js.map +7 -0
  6. package/dist/custom/button.d.ts +2 -0
  7. package/dist/custom/button.d.ts.map +1 -0
  8. package/dist/custom/button.js +4 -0
  9. package/dist/custom/button.js.map +7 -0
  10. package/dist/custom/index.d.ts +3 -0
  11. package/dist/custom/index.d.ts.map +1 -0
  12. package/dist/custom/index.js +2 -0
  13. package/dist/custom/index.js.map +7 -0
  14. package/dist/custom/path.d.ts +14 -0
  15. package/dist/custom/path.d.ts.map +1 -0
  16. package/dist/custom/path.js +20 -0
  17. package/dist/custom/path.js.map +7 -0
  18. package/dist/error/errorOverlay.d.ts +3 -0
  19. package/dist/error/errorOverlay.d.ts.map +1 -0
  20. package/dist/error/errorOverlay.js +84 -0
  21. package/dist/error/errorOverlay.js.map +7 -0
  22. package/dist/fynix/index.d.ts +6 -0
  23. package/dist/fynix/index.d.ts.map +1 -0
  24. package/dist/fynix/index.js +5 -0
  25. package/dist/fynix/index.js.map +7 -0
  26. package/dist/hooks/nixAsync.d.ts +20 -0
  27. package/dist/hooks/nixAsync.d.ts.map +1 -0
  28. package/dist/hooks/nixAsync.js +114 -0
  29. package/dist/hooks/nixAsync.js.map +7 -0
  30. package/dist/hooks/nixAsyncCache.d.ts +19 -0
  31. package/dist/hooks/nixAsyncCache.d.ts.map +1 -0
  32. package/dist/hooks/nixAsyncCache.js +137 -0
  33. package/dist/hooks/nixAsyncCache.js.map +7 -0
  34. package/dist/hooks/nixAsyncDebounce.d.ts +22 -0
  35. package/dist/hooks/nixAsyncDebounce.d.ts.map +1 -0
  36. package/dist/hooks/nixAsyncDebounce.js +77 -0
  37. package/dist/hooks/nixAsyncDebounce.js.map +7 -0
  38. package/dist/hooks/nixAsyncQuery.d.ts +16 -0
  39. package/dist/hooks/nixAsyncQuery.d.ts.map +1 -0
  40. package/dist/hooks/nixAsyncQuery.js +87 -0
  41. package/dist/hooks/nixAsyncQuery.js.map +7 -0
  42. package/dist/hooks/nixCallback.d.ts +2 -0
  43. package/dist/hooks/nixCallback.d.ts.map +1 -0
  44. package/dist/hooks/nixCallback.js +34 -0
  45. package/dist/hooks/nixCallback.js.map +7 -0
  46. package/dist/hooks/nixComputed.d.ts +16 -0
  47. package/dist/hooks/nixComputed.d.ts.map +1 -0
  48. package/dist/hooks/nixComputed.js +175 -0
  49. package/dist/hooks/nixComputed.js.map +7 -0
  50. package/dist/hooks/nixDebounce.d.ts +11 -0
  51. package/dist/hooks/nixDebounce.d.ts.map +1 -0
  52. package/dist/hooks/nixDebounce.js +55 -0
  53. package/dist/hooks/nixDebounce.js.map +7 -0
  54. package/dist/hooks/nixEffect.d.ts +4 -0
  55. package/dist/hooks/nixEffect.d.ts.map +1 -0
  56. package/dist/hooks/nixEffect.js +75 -0
  57. package/dist/hooks/nixEffect.js.map +7 -0
  58. package/dist/hooks/nixFor.d.ts +13 -0
  59. package/dist/hooks/nixFor.d.ts.map +1 -0
  60. package/dist/hooks/nixFor.js +43 -0
  61. package/dist/hooks/nixFor.js.map +7 -0
  62. package/dist/hooks/nixForm.d.ts +33 -0
  63. package/dist/hooks/nixForm.d.ts.map +1 -0
  64. package/dist/hooks/nixForm.js +123 -0
  65. package/dist/hooks/nixForm.js.map +7 -0
  66. package/dist/hooks/nixFormAsync.d.ts +42 -0
  67. package/dist/hooks/nixFormAsync.d.ts.map +1 -0
  68. package/dist/hooks/nixFormAsync.js +169 -0
  69. package/dist/hooks/nixFormAsync.js.map +7 -0
  70. package/dist/hooks/nixInterval.d.ts +2 -0
  71. package/dist/hooks/nixInterval.d.ts.map +1 -0
  72. package/dist/hooks/nixInterval.js +23 -0
  73. package/dist/hooks/nixInterval.js.map +7 -0
  74. package/dist/hooks/nixLazy.d.ts +8 -0
  75. package/dist/hooks/nixLazy.d.ts.map +1 -0
  76. package/dist/hooks/nixLazy.js +58 -0
  77. package/dist/hooks/nixLazy.js.map +7 -0
  78. package/dist/hooks/nixLazyAsync.d.ts +10 -0
  79. package/dist/hooks/nixLazyAsync.d.ts.map +1 -0
  80. package/dist/hooks/nixLazyAsync.js +71 -0
  81. package/dist/hooks/nixLazyAsync.js.map +7 -0
  82. package/dist/hooks/nixLazyFormAsync.d.ts +50 -0
  83. package/dist/hooks/nixLazyFormAsync.d.ts.map +1 -0
  84. package/dist/hooks/nixLazyFormAsync.js +221 -0
  85. package/dist/hooks/nixLazyFormAsync.js.map +7 -0
  86. package/dist/hooks/nixLocalStorage.d.ts +8 -0
  87. package/dist/hooks/nixLocalStorage.d.ts.map +1 -0
  88. package/dist/hooks/nixLocalStorage.js +136 -0
  89. package/dist/hooks/nixLocalStorage.js.map +7 -0
  90. package/dist/hooks/nixMemo.d.ts +2 -0
  91. package/dist/hooks/nixMemo.d.ts.map +1 -0
  92. package/dist/hooks/nixMemo.js +30 -0
  93. package/dist/hooks/nixMemo.js.map +7 -0
  94. package/dist/hooks/nixPrevious.d.ts +2 -0
  95. package/dist/hooks/nixPrevious.d.ts.map +1 -0
  96. package/dist/hooks/nixPrevious.js +15 -0
  97. package/dist/hooks/nixPrevious.js.map +7 -0
  98. package/dist/hooks/nixRef.d.ts +4 -0
  99. package/dist/hooks/nixRef.d.ts.map +1 -0
  100. package/dist/hooks/nixRef.js +17 -0
  101. package/dist/hooks/nixRef.js.map +7 -0
  102. package/dist/hooks/nixState.d.ts +15 -0
  103. package/dist/hooks/nixState.d.ts.map +1 -0
  104. package/dist/hooks/nixState.js +127 -0
  105. package/dist/hooks/nixState.js.map +7 -0
  106. package/dist/hooks/nixStore.d.ts +10 -0
  107. package/dist/hooks/nixStore.d.ts.map +1 -0
  108. package/dist/hooks/nixStore.js +103 -0
  109. package/dist/hooks/nixStore.js.map +7 -0
  110. package/dist/package.json +221 -0
  111. package/dist/plugins/vite-plugin-res.d.ts +38 -0
  112. package/dist/plugins/vite-plugin-res.d.ts.map +1 -0
  113. package/dist/plugins/vite-plugin-res.js +106 -0
  114. package/dist/plugins/vite-plugin-res.js.map +7 -0
  115. package/dist/router/router.d.ts +48 -0
  116. package/dist/router/router.d.ts.map +1 -0
  117. package/dist/router/router.js +872 -0
  118. package/dist/router/router.js.map +7 -0
  119. package/dist/runtime.d.ts +124 -0
  120. package/dist/runtime.d.ts.map +1 -0
  121. package/dist/runtime.js +1361 -0
  122. package/dist/runtime.js.map +7 -0
  123. package/package.json +254 -0
  124. package/types/fnx.d.ts +34 -0
  125. package/types/fynix-ui.d.ts +323 -0
  126. package/types/global.d.ts +279 -0
  127. package/types/index.d.ts +37 -0
  128. package/types/jsx.d.ts +993 -0
  129. package/types/vite-env.d.ts +545 -0
@@ -0,0 +1,175 @@
1
+ import { activeContext, setActiveContext } from "../context/context";
2
+ export function nixComputed(computeFn) {
3
+ const ctx = activeContext;
4
+ if (!ctx)
5
+ throw new Error("nixComputed() called outside component");
6
+ if (typeof computeFn !== "function") {
7
+ throw new TypeError("[nixComputed] First argument must be a function");
8
+ }
9
+ const idx = ctx.hookIndex++;
10
+ if (!ctx.hooks[idx]) {
11
+ const subscribers = new Set();
12
+ const dependencies = new Set();
13
+ const unsubscribers = new Map();
14
+ let cachedValue;
15
+ let isStale = true;
16
+ let isDestroyed = false;
17
+ let isComputing = false;
18
+ function compute() {
19
+ if (isDestroyed)
20
+ return cachedValue;
21
+ if (isComputing) {
22
+ console.error("[nixComputed] Circular dependency detected");
23
+ return cachedValue;
24
+ }
25
+ isComputing = true;
26
+ const trackingContext = {
27
+ _accessedStates: new Set(),
28
+ hookIndex: 0,
29
+ hooks: [],
30
+ _subscriptions: new Set(),
31
+ _subscriptionCleanups: [],
32
+ effects: [],
33
+ cleanups: [],
34
+ _vnode: null,
35
+ version: 0,
36
+ props: {},
37
+ stateCleanups: [],
38
+ parent: null,
39
+ context: {},
40
+ rerender: () => { },
41
+ Component: null,
42
+ _isMounted: false,
43
+ _isRerendering: false,
44
+ };
45
+ const prevContext = activeContext;
46
+ try {
47
+ setActiveContext(trackingContext);
48
+ cachedValue = computeFn();
49
+ const oldDeps = Array.from(dependencies);
50
+ oldDeps.forEach((dep) => {
51
+ if (!trackingContext._accessedStates.has(dep)) {
52
+ if (unsubscribers.has(dep)) {
53
+ try {
54
+ unsubscribers.get(dep)();
55
+ }
56
+ catch (e) {
57
+ console.error("[nixComputed] Error unsubscribing from old dependency:", e);
58
+ }
59
+ unsubscribers.delete(dep);
60
+ }
61
+ dependencies.delete(dep);
62
+ }
63
+ });
64
+ trackingContext._accessedStates.forEach((state) => {
65
+ if (!dependencies.has(state)) {
66
+ const unsub = state.subscribe(() => {
67
+ if (isDestroyed)
68
+ return;
69
+ isStale = true;
70
+ const newValue = s.value;
71
+ const subsArray = Array.from(subscribers);
72
+ subsArray.forEach((fn) => {
73
+ try {
74
+ fn(newValue);
75
+ }
76
+ catch (e) {
77
+ console.error("[nixComputed] Subscriber error:", e);
78
+ subscribers.delete(fn);
79
+ }
80
+ });
81
+ });
82
+ unsubscribers.set(state, unsub);
83
+ dependencies.add(state);
84
+ }
85
+ });
86
+ isStale = false;
87
+ }
88
+ catch (err) {
89
+ console.error("[nixComputed] Compute error:", err);
90
+ isStale = false;
91
+ }
92
+ finally {
93
+ setActiveContext(prevContext);
94
+ isComputing = false;
95
+ }
96
+ return cachedValue;
97
+ }
98
+ const s = {
99
+ get value() {
100
+ if (isDestroyed) {
101
+ console.warn("[nixComputed] Accessing destroyed computed state");
102
+ return cachedValue;
103
+ }
104
+ if (isStale) {
105
+ compute();
106
+ }
107
+ if (activeContext && activeContext._accessedStates) {
108
+ activeContext._accessedStates.add(s);
109
+ }
110
+ return cachedValue;
111
+ },
112
+ subscribe(fn) {
113
+ if (typeof fn !== "function") {
114
+ console.error("[nixComputed] subscribe() requires a function");
115
+ return () => { };
116
+ }
117
+ if (isDestroyed) {
118
+ console.warn("[nixComputed] Cannot subscribe to destroyed computed state");
119
+ return () => { };
120
+ }
121
+ const MAX_SUBSCRIBERS = 1000;
122
+ if (subscribers.size >= MAX_SUBSCRIBERS) {
123
+ console.error("[nixComputed] Maximum subscriber limit reached");
124
+ return () => { };
125
+ }
126
+ subscribers.add(fn);
127
+ return () => {
128
+ subscribers.delete(fn);
129
+ };
130
+ },
131
+ cleanup() {
132
+ if (isDestroyed)
133
+ return;
134
+ isDestroyed = true;
135
+ unsubscribers.forEach((unsub) => {
136
+ try {
137
+ unsub();
138
+ }
139
+ catch (e) {
140
+ console.error("[nixComputed] Cleanup error:", e);
141
+ }
142
+ });
143
+ unsubscribers.clear();
144
+ dependencies.clear();
145
+ subscribers.clear();
146
+ cachedValue = null;
147
+ console.log("[nixComputed] Computed state cleaned up");
148
+ },
149
+ getSubscriberCount() {
150
+ return subscribers.size;
151
+ },
152
+ getDependencyCount() {
153
+ return dependencies.size;
154
+ },
155
+ isDestroyed() {
156
+ return isDestroyed;
157
+ },
158
+ getDependencyInfo() {
159
+ return Array.from(dependencies).map((state) => ({
160
+ state,
161
+ hasCleanup: unsubscribers.has(state),
162
+ isComputed: !!state._isComputed,
163
+ }));
164
+ },
165
+ _isNixState: true,
166
+ _isComputed: true,
167
+ };
168
+ compute();
169
+ ctx.hooks[idx] = s;
170
+ if (ctx.stateCleanups) {
171
+ ctx.stateCleanups.push(() => s.cleanup());
172
+ }
173
+ }
174
+ return ctx.hooks[idx];
175
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../hooks/nixComputed.ts"],
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/* ----------------------\r\n nixComputed - Computed/Derived State\r\n Memory Leaks & Security Issues Resolved\r\n---------------------- */\r\nimport { activeContext, setActiveContext } from \"../context/context\";\r\n\r\n/**\r\n * @template T\r\n * @typedef {Object} ComputedState\r\n * @property {T} value - Get the computed value (read-only)\r\n * @property {(fn: (value: T) => void) => (() => void)} subscribe - Subscribe to computed value changes\r\n * @property {() => void} cleanup - Cleanup all subscriptions and dependencies\r\n * @property {() => number} getSubscriberCount - Get number of active subscribers (debugging)\r\n * @property {() => number} getDependencyCount - Get number of tracked dependencies (debugging)\r\n * @property {() => boolean} isDestroyed - Check if computed state has been destroyed\r\n * @property {boolean} _isNixState - Internal flag (computed states behave like states)\r\n * @property {boolean} _isComputed - Internal flag to identify computed states\r\n */\r\n\r\n/**\r\n * Create a derived/computed state from other states.\r\n * Automatically tracks dependencies and updates when any dependency changes.\r\n *\r\n * @template T\r\n * @param {() => T} computeFn - Function that computes the derived value\r\n * @returns {ComputedState<T>} A reactive state object with the computed value\r\n *\r\n * @example\r\n * const count = nixState(5);\r\n * const doubled = nixComputed(() => count.value * 2);\r\n * console.log(doubled.value); // 10\r\n * count.value = 10;\r\n * console.log(doubled.value); // 20\r\n *\r\n * @example\r\n * // Multiple dependencies\r\n * const a = nixState(5);\r\n * const b = nixState(10);\r\n * const sum = nixComputed(() => a.value + b.value);\r\n * console.log(sum.value); // 15\r\n *\r\n * @example\r\n * // Conditional dependencies\r\n * const flag = nixState(true);\r\n * const x = nixState(1);\r\n * const y = nixState(2);\r\n * const result = nixComputed(() => flag.value ? x.value : y.value);\r\n *\r\n * @example\r\n * // With cleanup\r\n * const MyComponent = () => {\r\n * const count = nixState(0);\r\n * const doubled = nixComputed(() => count.value * 2);\r\n *\r\n * nixEffect(() => {\r\n * return () => {\r\n * doubled.cleanup();\r\n * count.cleanup();\r\n * };\r\n * }, []);\r\n * };\r\n *\r\n * @throws {Error} If called outside a component context\r\n * @throws {TypeError} If computeFn is not a function\r\n */\r\nexport function nixComputed<T>(computeFn: () => T): {\r\n value: T;\r\n subscribe: (fn: (value: T) => void) => () => void;\r\n cleanup: () => void;\r\n getSubscriberCount: () => number;\r\n getDependencyCount: () => number;\r\n isDestroyed: () => boolean;\r\n getDependencyInfo: () => Array<{\r\n state: any;\r\n hasCleanup: boolean;\r\n isComputed: boolean;\r\n }>;\r\n _isNixState: true;\r\n _isComputed: true;\r\n} {\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<any>;\r\n stateCleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixComputed() called outside component\");\r\n\r\n if (typeof computeFn !== \"function\") {\r\n throw new TypeError(\"[nixComputed] First argument must be a function\");\r\n }\r\n\r\n const idx = ctx.hookIndex++;\r\n if (!ctx.hooks[idx]) {\r\n const subscribers: Set<(value: T) => void> = new Set();\r\n const dependencies: Set<any> = new Set();\r\n const unsubscribers: Map<any, () => void> = new Map();\r\n let cachedValue: T;\r\n let isStale = true;\r\n let isDestroyed = false;\r\n let isComputing = false;\r\n\r\n function compute(): T {\r\n if (isDestroyed) return cachedValue;\r\n if (isComputing) {\r\n console.error(\"[nixComputed] Circular dependency detected\");\r\n return cachedValue;\r\n }\r\n isComputing = true;\r\n // Provide all required properties for ComponentContext type\r\n // Use type assertion for safety and future-proofing\r\n const trackingContext = {\r\n _accessedStates: new Set(),\r\n hookIndex: 0,\r\n hooks: [],\r\n _subscriptions: new Set(),\r\n _subscriptionCleanups: [],\r\n effects: [],\r\n cleanups: [],\r\n _vnode: null,\r\n version: 0,\r\n props: {},\r\n stateCleanups: [],\r\n parent: null,\r\n context: {},\r\n rerender: () => {},\r\n Component: null,\r\n _isMounted: false,\r\n _isRerendering: false,\r\n } as any; // Use 'as any' to satisfy ComponentContext type\r\n const prevContext = activeContext;\r\n try {\r\n setActiveContext(trackingContext);\r\n cachedValue = computeFn();\r\n const oldDeps = Array.from(dependencies);\r\n oldDeps.forEach((dep) => {\r\n if (!trackingContext._accessedStates.has(dep)) {\r\n if (unsubscribers.has(dep)) {\r\n try {\r\n unsubscribers.get(dep)!();\r\n } catch (e) {\r\n console.error(\r\n \"[nixComputed] Error unsubscribing from old dependency:\",\r\n e\r\n );\r\n }\r\n unsubscribers.delete(dep);\r\n }\r\n dependencies.delete(dep);\r\n }\r\n });\r\n trackingContext._accessedStates.forEach((state: any) => {\r\n if (!dependencies.has(state)) {\r\n const unsub = state.subscribe(() => {\r\n if (isDestroyed) return;\r\n\r\n isStale = true;\r\n // Get fresh value when dependency changes\r\n const newValue = s.value;\r\n const subsArray = Array.from(subscribers);\r\n subsArray.forEach((fn) => {\r\n try {\r\n fn(newValue);\r\n } catch (e) {\r\n console.error(\"[nixComputed] Subscriber error:\", e);\r\n subscribers.delete(fn);\r\n }\r\n });\r\n });\r\n unsubscribers.set(state, unsub);\r\n dependencies.add(state);\r\n }\r\n });\r\n isStale = false;\r\n } catch (err) {\r\n console.error(\"[nixComputed] Compute error:\", err);\r\n isStale = false;\r\n } finally {\r\n setActiveContext(prevContext);\r\n isComputing = false;\r\n }\r\n return cachedValue;\r\n }\r\n\r\n // All cleanup functionality is now part of the s object below\r\n\r\n const s = {\r\n get value(): T {\r\n if (isDestroyed) {\r\n console.warn(\"[nixComputed] Accessing destroyed computed state\");\r\n return cachedValue;\r\n }\r\n if (isStale) {\r\n compute();\r\n }\r\n if (activeContext && (activeContext as any)._accessedStates) {\r\n (activeContext as any)._accessedStates.add(s);\r\n }\r\n return cachedValue;\r\n },\r\n subscribe(fn: (value: T) => void): () => void {\r\n if (typeof fn !== \"function\") {\r\n console.error(\"[nixComputed] subscribe() requires a function\");\r\n return () => {};\r\n }\r\n if (isDestroyed) {\r\n console.warn(\r\n \"[nixComputed] Cannot subscribe to destroyed computed state\"\r\n );\r\n return () => {};\r\n }\r\n const MAX_SUBSCRIBERS = 1000;\r\n if (subscribers.size >= MAX_SUBSCRIBERS) {\r\n console.error(\"[nixComputed] Maximum subscriber limit reached\");\r\n return () => {};\r\n }\r\n subscribers.add(fn);\r\n return () => {\r\n subscribers.delete(fn);\r\n };\r\n },\r\n cleanup(): void {\r\n if (isDestroyed) return;\r\n isDestroyed = true;\r\n unsubscribers.forEach((unsub) => {\r\n try {\r\n unsub();\r\n } catch (e) {\r\n console.error(\"[nixComputed] Cleanup error:\", e);\r\n }\r\n });\r\n unsubscribers.clear();\r\n dependencies.clear();\r\n subscribers.clear();\r\n cachedValue = null as any as T;\r\n console.log(\"[nixComputed] Computed state cleaned up\");\r\n },\r\n getSubscriberCount(): number {\r\n return subscribers.size;\r\n },\r\n getDependencyCount(): number {\r\n return dependencies.size;\r\n },\r\n isDestroyed(): boolean {\r\n return isDestroyed;\r\n },\r\n getDependencyInfo(): Array<{\r\n state: any;\r\n hasCleanup: boolean;\r\n isComputed: boolean;\r\n }> {\r\n return Array.from(dependencies).map((state) => ({\r\n state,\r\n hasCleanup: unsubscribers.has(state),\r\n isComputed: !!state._isComputed,\r\n }));\r\n },\r\n _isNixState: true as const,\r\n _isComputed: true as const,\r\n };\r\n\r\n compute();\r\n ctx.hooks[idx] = s;\r\n if (ctx.stateCleanups) {\r\n ctx.stateCleanups.push(() => s.cleanup());\r\n }\r\n }\r\n return ctx.hooks[idx] as ReturnType<typeof nixComputed<T>>;\r\n}\r\n"],
5
+ "mappings": "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",
6
+ "names": ["compute"]
7
+ }
@@ -0,0 +1,11 @@
1
+ export interface NixDebounceOptions {
2
+ leading?: boolean;
3
+ trailing?: boolean;
4
+ maxWait?: number;
5
+ signal?: AbortSignal;
6
+ }
7
+ export type DebouncedFunction<T extends (...args: any[]) => any> = ((...args: Parameters<T>) => void) & {
8
+ cancel: () => void;
9
+ };
10
+ export declare function nixDebounce<T extends (...args: any[]) => any>(fn: T, delay?: number, options?: NixDebounceOptions): DebouncedFunction<T>;
11
+ //# sourceMappingURL=nixDebounce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nixDebounce.d.ts","sourceRoot":"","sources":["../../hooks/nixDebounce.ts"],"names":[],"mappings":"AAiDA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC,CAClE,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KACnB,IAAI,CAAC,GAAG;IACX,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC3D,EAAE,EAAE,CAAC,EACL,KAAK,GAAE,MAAY,EACnB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAAC,CAAC,CAAC,CA8DtB"}
@@ -0,0 +1,55 @@
1
+ export function nixDebounce(fn, delay = 300, options = {}) {
2
+ let timerId = null;
3
+ let lastInvokeTime = 0;
4
+ let lastArgs = null;
5
+ let lastThis = null;
6
+ const { leading = false, trailing = true, maxWait, signal } = options;
7
+ if (signal) {
8
+ signal.addEventListener("abort", () => {
9
+ if (timerId) {
10
+ clearTimeout(timerId);
11
+ timerId = null;
12
+ }
13
+ lastArgs = null;
14
+ lastThis = null;
15
+ });
16
+ }
17
+ const invoke = () => {
18
+ lastInvokeTime = Date.now();
19
+ if (lastArgs) {
20
+ fn.apply(lastThis, lastArgs);
21
+ lastArgs = lastThis = null;
22
+ }
23
+ };
24
+ const debounced = function (...args) {
25
+ const now = Date.now();
26
+ lastArgs = args;
27
+ lastThis = this;
28
+ const shouldInvokeLeading = leading && !timerId;
29
+ const timeSinceLastInvoke = now - lastInvokeTime;
30
+ const remainingTime = delay - timeSinceLastInvoke;
31
+ if (maxWait !== undefined && timeSinceLastInvoke >= maxWait) {
32
+ if (timerId)
33
+ clearTimeout(timerId);
34
+ timerId = null;
35
+ invoke();
36
+ return;
37
+ }
38
+ if (timerId)
39
+ clearTimeout(timerId);
40
+ if (shouldInvokeLeading) {
41
+ invoke();
42
+ }
43
+ if (trailing) {
44
+ timerId = setTimeout(invoke, remainingTime > 0 ? remainingTime : delay);
45
+ }
46
+ };
47
+ debounced.cancel = () => {
48
+ if (timerId)
49
+ clearTimeout(timerId);
50
+ timerId = null;
51
+ lastArgs = null;
52
+ lastThis = null;
53
+ };
54
+ return debounced;
55
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../hooks/nixDebounce.ts"],
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/**\r\n * @fileoverview Debounce utility function with advanced options.\r\n * Supports leading/trailing edge invocation, maxWait, and AbortController.\r\n */\r\n\r\n/**\r\n * Debounce a function with options for leading, trailing, maxWait, and AbortController support.\r\n *\r\n * @param {Function} fn - The function to debounce\r\n * @param {number} [delay=300] - Delay in milliseconds\r\n * @param {Object} [options={}] - Debounce options\r\n * @param {boolean} [options.leading=false] - Invoke on the leading edge\r\n * @param {boolean} [options.trailing=true] - Invoke on the trailing edge\r\n * @param {number} [options.maxWait] - Maximum wait time before forced invocation\r\n * @param {AbortSignal} [options.signal] - Optional AbortSignal to cancel pending calls\r\n * @returns {Function} Debounced function with `.cancel()` method\r\n *\r\n * @example\r\n * const controller = new AbortController();\r\n * const debounced = nixDebounce(() => console.log('Hello'), 500, {\r\n * leading: true,\r\n * maxWait: 2000,\r\n * signal: controller.signal\r\n * });\r\n * debounced();\r\n * controller.abort(); // Cancel pending invocation\r\n */\r\nexport interface NixDebounceOptions {\r\n leading?: boolean;\r\n trailing?: boolean;\r\n maxWait?: number;\r\n signal?: AbortSignal;\r\n}\r\n\r\nexport type DebouncedFunction<T extends (...args: any[]) => any> = ((\r\n ...args: Parameters<T>\r\n) => void) & {\r\n cancel: () => void;\r\n};\r\n\r\nexport function nixDebounce<T extends (...args: any[]) => any>(\r\n fn: T,\r\n delay: number = 300,\r\n options: NixDebounceOptions = {}\r\n): DebouncedFunction<T> {\r\n let timerId: ReturnType<typeof setTimeout> | null = null;\r\n let lastInvokeTime = 0;\r\n let lastArgs: Parameters<T> | null = null;\r\n let lastThis: any = null;\r\n\r\n const { leading = false, trailing = true, maxWait, signal } = options;\r\n\r\n if (signal) {\r\n signal.addEventListener(\"abort\", () => {\r\n if (timerId) {\r\n clearTimeout(timerId);\r\n timerId = null;\r\n }\r\n lastArgs = null;\r\n lastThis = null;\r\n });\r\n }\r\n\r\n const invoke = () => {\r\n lastInvokeTime = Date.now();\r\n if (lastArgs) {\r\n fn.apply(lastThis, lastArgs);\r\n lastArgs = lastThis = null;\r\n }\r\n };\r\n\r\n const debounced = function (this: unknown, ...args: Parameters<T>) {\r\n const now = Date.now();\r\n lastArgs = args;\r\n lastThis = this;\r\n\r\n const shouldInvokeLeading = leading && !timerId;\r\n const timeSinceLastInvoke = now - lastInvokeTime;\r\n const remainingTime = delay - timeSinceLastInvoke;\r\n\r\n if (maxWait !== undefined && timeSinceLastInvoke >= maxWait) {\r\n if (timerId) clearTimeout(timerId);\r\n timerId = null;\r\n invoke();\r\n return;\r\n }\r\n\r\n if (timerId) clearTimeout(timerId);\r\n\r\n if (shouldInvokeLeading) {\r\n invoke();\r\n }\r\n\r\n if (trailing) {\r\n timerId = setTimeout(invoke, remainingTime > 0 ? remainingTime : delay);\r\n }\r\n } as DebouncedFunction<T>;\r\n\r\n debounced.cancel = () => {\r\n if (timerId) clearTimeout(timerId);\r\n timerId = null;\r\n lastArgs = null;\r\n lastThis = null;\r\n };\r\n\r\n return debounced;\r\n}\r\n"],
5
+ "mappings": ";;AA8DO,SAAS,YACd,IACA,QAAgB,KAChB,UAA8B,CAAC,GACT;AACtB,MAAI,UAAgD;AACpD,MAAI,iBAAiB;AACrB,MAAI,WAAiC;AACrC,MAAI,WAAgB;AAEpB,QAAM,EAAE,UAAU,OAAO,WAAW,MAAM,SAAS,OAAO,IAAI;AAE9D,MAAI,QAAQ;AACV,WAAO,iBAAiB,SAAS,MAAM;AACrC,UAAI,SAAS;AACX,qBAAa,OAAO;AACpB,kBAAU;AAAA,MACZ;AACA,iBAAW;AACX,iBAAW;AAAA,IACb,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,6BAAM;AACnB,qBAAiB,KAAK,IAAI;AAC1B,QAAI,UAAU;AACZ,SAAG,MAAM,UAAU,QAAQ;AAC3B,iBAAW,WAAW;AAAA,IACxB;AAAA,EACF,GANe;AAQf,QAAM,YAAY,mCAA4B,MAAqB;AACjE,UAAM,MAAM,KAAK,IAAI;AACrB,eAAW;AACX,eAAW;AAEX,UAAM,sBAAsB,WAAW,CAAC;AACxC,UAAM,sBAAsB,MAAM;AAClC,UAAM,gBAAgB,QAAQ;AAE9B,QAAI,YAAY,UAAa,uBAAuB,SAAS;AAC3D,UAAI,QAAS,cAAa,OAAO;AACjC,gBAAU;AACV,aAAO;AACP;AAAA,IACF;AAEA,QAAI,QAAS,cAAa,OAAO;AAEjC,QAAI,qBAAqB;AACvB,aAAO;AAAA,IACT;AAEA,QAAI,UAAU;AACZ,gBAAU,WAAW,QAAQ,gBAAgB,IAAI,gBAAgB,KAAK;AAAA,IACxE;AAAA,EACF,GAzBkB;AA2BlB,YAAU,SAAS,MAAM;AACvB,QAAI,QAAS,cAAa,OAAO;AACjC,cAAU;AACV,eAAW;AACX,eAAW;AAAA,EACb;AAEA,SAAO;AACT;AAlEgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,4 @@
1
+ export declare function nixEffect(effect: () => void | (() => void), deps?: any[]): void;
2
+ export declare function nixEffectOnce(effect: () => void | (() => void)): void;
3
+ export declare function nixEffectAlways(effect: () => void | (() => void)): void;
4
+ //# sourceMappingURL=nixEffect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nixEffect.d.ts","sourceRoot":"","sources":["../../hooks/nixEffect.ts"],"names":[],"mappings":"AAmEA,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,EACjC,IAAI,GAAE,GAAG,EAAO,GACf,IAAI,CAyDN;AAgCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAErE;AAaD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAUvE"}
@@ -0,0 +1,75 @@
1
+ import { activeContext } from "../context/context";
2
+ export function nixEffect(effect, deps = []) {
3
+ const ctx = activeContext;
4
+ if (!ctx)
5
+ throw new Error("nixEffect() called outside component");
6
+ if (typeof effect !== "function") {
7
+ console.error("[nixEffect] First argument must be a function");
8
+ return;
9
+ }
10
+ if (!Array.isArray(deps)) {
11
+ console.error("[nixEffect] Second argument must be an array");
12
+ deps = [];
13
+ }
14
+ const MAX_DEPS = 100;
15
+ if (deps.length > MAX_DEPS) {
16
+ console.warn(`[nixEffect] Dependency array too large (${deps.length}). Limited to ${MAX_DEPS}.`);
17
+ deps = deps.slice(0, MAX_DEPS);
18
+ }
19
+ const idx = ctx.hookIndex++;
20
+ const prev = ctx.hooks[idx];
21
+ const hasChanged = !prev || !shallowArrayEqual(prev.deps, deps);
22
+ if (hasChanged) {
23
+ if (prev?.cleanup) {
24
+ try {
25
+ if (typeof prev.cleanup === "function") {
26
+ prev.cleanup();
27
+ }
28
+ }
29
+ catch (err) {
30
+ console.error("[nixEffect] Cleanup error:", err);
31
+ }
32
+ }
33
+ let cleanup;
34
+ try {
35
+ cleanup = effect();
36
+ if (cleanup !== undefined && typeof cleanup !== "function") {
37
+ console.warn("[nixEffect] Effect should return undefined or a cleanup function");
38
+ cleanup = undefined;
39
+ }
40
+ }
41
+ catch (err) {
42
+ console.error("[nixEffect] Effect error:", err);
43
+ cleanup = undefined;
44
+ }
45
+ ctx.hooks[idx] = { deps, cleanup };
46
+ if (cleanup && typeof cleanup === "function") {
47
+ if (!ctx.cleanups)
48
+ ctx.cleanups = [];
49
+ ctx.cleanups.push(cleanup);
50
+ }
51
+ }
52
+ }
53
+ function shallowArrayEqual(arr1, arr2) {
54
+ if (arr1.length !== arr2.length)
55
+ return false;
56
+ for (let i = 0; i < arr1.length; i++) {
57
+ if (!Object.is(arr1[i], arr2[i])) {
58
+ return false;
59
+ }
60
+ }
61
+ return true;
62
+ }
63
+ export function nixEffectOnce(effect) {
64
+ return nixEffect(effect, []);
65
+ }
66
+ export function nixEffectAlways(effect) {
67
+ const ctx = activeContext;
68
+ if (!ctx)
69
+ throw new Error("nixEffectAlways() called outside component");
70
+ if (typeof effect !== "function") {
71
+ console.error("[nixEffectAlways] Argument must be a function");
72
+ return;
73
+ }
74
+ return nixEffect(effect, [ctx.version]);
75
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../hooks/nixEffect.ts"],
4
+ "sourcesContent": ["/* MIT License\r\n\r\n* Copyright (c) 2026 Resty Gonzales\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\n* SOFTWARE.\r\n */\r\n/* ----------------------\r\n nixEffect - Side Effects Hook\r\n Memory Leaks & Security Issues Resolved\r\n---------------------- */\r\nimport { activeContext } from \"../context/context\";\r\n\r\n/**\r\n * Execute side effects in a component with automatic cleanup and security enhancements.\r\n * Similar to React's useEffect but with enhanced security and memory leak prevention.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function, optionally returns cleanup function\r\n * @param {Array<any>} [deps=[]] - Dependency array. Effect re-runs when dependencies change.\r\n * @param {object} [options={}] - Configuration options\r\n *\r\n * @example\r\n * // Run once on mount\r\n * nixEffect(() => {\r\n * console.log('Component mounted');\r\n * return () => console.log('Component unmounted');\r\n * }, []);\r\n *\r\n * @example\r\n * // Run when count changes with timeout\r\n * const count = nixState(0);\r\n * nixEffect(() => {\r\n * console.log('Count is:', count.value);\r\n * }, [count.value], { timeout: 5000 });\r\n *\r\n * @example\r\n * // Timer with cleanup\r\n * nixEffect(() => {\r\n * const timer = setInterval(() => console.log('tick'), 1000);\r\n * return () => clearInterval(timer);\r\n * }, []);\r\n *\r\n * @example\r\n * // Event listener with cleanup\r\n * nixEffect(() => {\r\n * const handler = (e) => console.log('clicked', e);\r\n * document.addEventListener('click', handler);\r\n * return () => document.removeEventListener('click', handler);\r\n * }, []);\r\n *\r\n * @throws {Error} If called outside a component context\r\n */\r\nexport function nixEffect(\r\n effect: () => void | (() => void),\r\n deps: any[] = []\r\n): void {\r\n const ctx = activeContext as\r\n | (typeof activeContext & {\r\n hookIndex: number;\r\n hooks: Array<any>;\r\n cleanups?: Array<() => void>;\r\n })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixEffect() called outside component\");\r\n\r\n if (typeof effect !== \"function\") {\r\n console.error(\"[nixEffect] First argument must be a function\");\r\n return;\r\n }\r\n if (!Array.isArray(deps)) {\r\n console.error(\"[nixEffect] Second argument must be an array\");\r\n deps = [];\r\n }\r\n const MAX_DEPS = 100;\r\n if (deps.length > MAX_DEPS) {\r\n console.warn(\r\n `[nixEffect] Dependency array too large (${deps.length}). Limited to ${MAX_DEPS}.`\r\n );\r\n deps = deps.slice(0, MAX_DEPS);\r\n }\r\n const idx = ctx.hookIndex++;\r\n const prev = ctx.hooks[idx];\r\n const hasChanged = !prev || !shallowArrayEqual(prev.deps, deps);\r\n if (hasChanged) {\r\n if (prev?.cleanup) {\r\n try {\r\n if (typeof prev.cleanup === \"function\") {\r\n prev.cleanup();\r\n }\r\n } catch (err) {\r\n console.error(\"[nixEffect] Cleanup error:\", err);\r\n }\r\n }\r\n let cleanup: void | (() => void);\r\n try {\r\n cleanup = effect();\r\n if (cleanup !== undefined && typeof cleanup !== \"function\") {\r\n console.warn(\r\n \"[nixEffect] Effect should return undefined or a cleanup function\"\r\n );\r\n cleanup = undefined;\r\n }\r\n } catch (err) {\r\n console.error(\"[nixEffect] Effect error:\", err);\r\n cleanup = undefined;\r\n }\r\n ctx.hooks[idx] = { deps, cleanup };\r\n if (cleanup && typeof cleanup === \"function\") {\r\n if (!ctx.cleanups) ctx.cleanups = [];\r\n ctx.cleanups.push(cleanup);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Shallow comparison of two arrays for dependency checking.\r\n * More reliable and secure than JSON.stringify.\r\n *\r\n * @param {Array<any>} arr1 - First array\r\n * @param {Array<any>} arr2 - Second array\r\n * @returns {boolean} True if arrays are shallowly equal\r\n */\r\nfunction shallowArrayEqual(arr1: any[], arr2: any[]): boolean {\r\n if (arr1.length !== arr2.length) return false;\r\n for (let i = 0; i < arr1.length; i++) {\r\n if (!Object.is(arr1[i], arr2[i])) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Run an effect only once on component mount.\r\n * Convenience wrapper around nixEffect.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function\r\n *\r\n * @example\r\n * nixEffectOnce(() => {\r\n * console.log('Mounted');\r\n * return () => console.log('Unmounted');\r\n * });\r\n */\r\nexport function nixEffectOnce(effect: () => void | (() => void)): void {\r\n return nixEffect(effect, []);\r\n}\r\n\r\n/**\r\n * Run an effect every time the component renders.\r\n * Use with caution - can cause performance issues.\r\n *\r\n * @param {() => (void | (() => void))} effect - Effect function\r\n *\r\n * @example\r\n * nixEffectAlways(() => {\r\n * console.log('Component rendered');\r\n * });\r\n */\r\nexport function nixEffectAlways(effect: () => void | (() => void)): void {\r\n const ctx = activeContext as\r\n | (typeof activeContext & { version: any })\r\n | undefined;\r\n if (!ctx) throw new Error(\"nixEffectAlways() called outside component\");\r\n if (typeof effect !== \"function\") {\r\n console.error(\"[nixEffectAlways] Argument must be a function\");\r\n return;\r\n }\r\n return nixEffect(effect, [ctx.version]);\r\n}\r\n"],
5
+ "mappings": ";;AA0BA,SAAS,qBAAqB;AAyCvB,SAAS,UACd,QACA,OAAc,CAAC,GACT;AACN,QAAM,MAAM;AAOZ,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAEhE,MAAI,OAAO,WAAW,YAAY;AAChC,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,YAAQ,MAAM,8CAA8C;AAC5D,WAAO,CAAC;AAAA,EACV;AACA,QAAM,WAAW;AACjB,MAAI,KAAK,SAAS,UAAU;AAC1B,YAAQ;AAAA,MACN,2CAA2C,KAAK,MAAM,iBAAiB,QAAQ;AAAA,IACjF;AACA,WAAO,KAAK,MAAM,GAAG,QAAQ;AAAA,EAC/B;AACA,QAAM,MAAM,IAAI;AAChB,QAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,QAAM,aAAa,CAAC,QAAQ,CAAC,kBAAkB,KAAK,MAAM,IAAI;AAC9D,MAAI,YAAY;AACd,QAAI,MAAM,SAAS;AACjB,UAAI;AACF,YAAI,OAAO,KAAK,YAAY,YAAY;AACtC,eAAK,QAAQ;AAAA,QACf;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,MAAM,8BAA8B,GAAG;AAAA,MACjD;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,OAAO;AACjB,UAAI,YAAY,UAAa,OAAO,YAAY,YAAY;AAC1D,gBAAQ;AAAA,UACN;AAAA,QACF;AACA,kBAAU;AAAA,MACZ;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,6BAA6B,GAAG;AAC9C,gBAAU;AAAA,IACZ;AACA,QAAI,MAAM,GAAG,IAAI,EAAE,MAAM,QAAQ;AACjC,QAAI,WAAW,OAAO,YAAY,YAAY;AAC5C,UAAI,CAAC,IAAI,SAAU,KAAI,WAAW,CAAC;AACnC,UAAI,SAAS,KAAK,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AA5DgB;AAsEhB,SAAS,kBAAkB,MAAa,MAAsB;AAC5D,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AARS;AAsBF,SAAS,cAAc,QAAyC;AACrE,SAAO,UAAU,QAAQ,CAAC,CAAC;AAC7B;AAFgB;AAeT,SAAS,gBAAgB,QAAyC;AACvE,QAAM,MAAM;AAGZ,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,4CAA4C;AACtE,MAAI,OAAO,WAAW,YAAY;AAChC,YAAQ,MAAM,+CAA+C;AAC7D;AAAA,EACF;AACA,SAAO,UAAU,QAAQ,CAAC,IAAI,OAAO,CAAC;AACxC;AAVgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ import { VNode } from "../types/fnx";
2
+ interface ReactiveState<T> {
3
+ value: T;
4
+ _isNixState: boolean;
5
+ subscribe(callback: (value: T) => void): () => void;
6
+ }
7
+ interface ForProps<T> {
8
+ each: T[] | ReactiveState<T[]>;
9
+ children?: ((item: T, index: number) => VNode) | ((item: T, index: number) => VNode)[];
10
+ }
11
+ export declare function For<T>(props: ForProps<T>): VNode;
12
+ export {};
13
+ //# sourceMappingURL=nixFor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nixFor.d.ts","sourceRoot":"","sources":["../../hooks/nixFor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,UAAU,aAAa,CAAC,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC;IACT,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACvD;AAED,UAAU,QAAQ,CAAC,CAAC;IAChB,IAAI,EAAE,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,EAAE,CAAC;CAC1F;AAkBD,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,CAoDhD"}
@@ -0,0 +1,43 @@
1
+ import { Fragment } from "../runtime";
2
+ export function For(props) {
3
+ let items = [];
4
+ if (props.each && typeof props.each === "object" && "_isNixState" in props.each) {
5
+ items = props.each.value;
6
+ }
7
+ else if (Array.isArray(props.each)) {
8
+ items = props.each;
9
+ }
10
+ let renderer;
11
+ if (typeof props.children === "function") {
12
+ renderer = props.children;
13
+ }
14
+ else if (Array.isArray(props.children)) {
15
+ const firstChild = props.children[0];
16
+ if (typeof firstChild === "function") {
17
+ renderer = firstChild;
18
+ }
19
+ }
20
+ if (!renderer) {
21
+ if (items.length > 0) {
22
+ console.warn("[Fynix] <For> expects a function as its child. Received:", typeof props.children);
23
+ }
24
+ return { type: Fragment, props: { children: [] }, key: null };
25
+ }
26
+ const mapped = items.map((item, index) => {
27
+ try {
28
+ return renderer(item, index);
29
+ }
30
+ catch (error) {
31
+ console.error(`[Fynix] Error rendering item at index ${index}:`, error);
32
+ return {
33
+ type: "div",
34
+ props: {
35
+ children: ["Error rendering item"],
36
+ style: "color: red;"
37
+ },
38
+ key: index
39
+ };
40
+ }
41
+ });
42
+ return { type: Fragment, props: { children: mapped }, key: null };
43
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../hooks/nixFor.ts"],
4
+ "sourcesContent": ["/* MIT License\r\n* Copyright (c) 2026 Resty Gonzales\r\n*/\r\n\r\nimport { Fragment } from \"../runtime\";\r\nimport { VNode } from \"../types/fnx\";\r\n\r\ninterface ReactiveState<T> {\r\n value: T;\r\n _isNixState: boolean;\r\n subscribe(callback: (value: T) => void): () => void;\r\n}\r\n\r\ninterface ForProps<T> {\r\n each: T[] | ReactiveState<T[]>;\r\n children?: ((item: T, index: number) => VNode) | ((item: T, index: number) => VNode)[];\r\n}\r\n\r\n/**\r\n * <For> Component\r\n *\r\n * A reactive list iteration component that efficiently renders items from an array or reactive state.\r\n *\r\n * @example\r\n * ```tsx\r\n * <For each={items}>\r\n * {(item, index) => <div key={index}>{item.name}</div>}\r\n * </For>\r\n * ```\r\n *\r\n * @param props - Component properties\r\n * @param props.each - The array or reactive state of array to iterate over\r\n * @param props.children - Render function that receives (item, index) and returns a VNode\r\n */\r\nexport function For<T>(props: ForProps<T>): VNode {\r\n // Extract items from reactive state or plain array\r\n let items: T[] = [];\r\n\r\n if (props.each && typeof props.each === \"object\" && \"_isNixState\" in props.each) {\r\n // Access .value to subscribe to reactive state\r\n items = (props.each as ReactiveState<T[]>).value;\r\n } else if (Array.isArray(props.each)) {\r\n items = props.each;\r\n }\r\n\r\n // Get the renderer function\r\n // In Fynix's JSX transform, children passed as {() => ...} becomes props.children directly\r\n let renderer: ((item: T, index: number) => VNode) | undefined;\r\n\r\n // Handle both direct function and array of children (depends on JSX transform)\r\n if (typeof props.children === \"function\") {\r\n renderer = props.children;\r\n } else if (Array.isArray(props.children)) {\r\n // If JSX transform wraps it in an array, get first element\r\n const firstChild = props.children[0];\r\n if (typeof firstChild === \"function\") {\r\n renderer = firstChild as (item: T, index: number) => VNode;\r\n }\r\n }\r\n\r\n if (!renderer) {\r\n if (items.length > 0) {\r\n console.warn(\"[Fynix] <For> expects a function as its child. Received:\", typeof props.children);\r\n }\r\n return { type: Fragment, props: { children: [] }, key: null };\r\n }\r\n\r\n // Map items to VNodes\r\n const mapped = items.map((item, index) => {\r\n try {\r\n return renderer!(item, index);\r\n } catch (error) {\r\n console.error(`[Fynix] Error rendering item at index ${index}:`, error);\r\n // Return a safe fallback\r\n return {\r\n type: \"div\",\r\n props: {\r\n children: [\"Error rendering item\"],\r\n style: \"color: red;\"\r\n },\r\n key: index\r\n } as VNode;\r\n }\r\n });\r\n\r\n return { type: Fragment, props: { children: mapped }, key: null };\r\n}"],
5
+ "mappings": ";;AAIA,SAAS,gBAAgB;AA8BlB,SAAS,IAAO,OAA2B;AAE9C,MAAI,QAAa,CAAC;AAElB,MAAI,MAAM,QAAQ,OAAO,MAAM,SAAS,YAAY,iBAAiB,MAAM,MAAM;AAE7E,YAAS,MAAM,KAA4B;AAAA,EAC/C,WAAW,MAAM,QAAQ,MAAM,IAAI,GAAG;AAClC,YAAQ,MAAM;AAAA,EAClB;AAIA,MAAI;AAGJ,MAAI,OAAO,MAAM,aAAa,YAAY;AACtC,eAAW,MAAM;AAAA,EACrB,WAAW,MAAM,QAAQ,MAAM,QAAQ,GAAG;AAEtC,UAAM,aAAa,MAAM,SAAS,CAAC;AACnC,QAAI,OAAO,eAAe,YAAY;AAClC,iBAAW;AAAA,IACf;AAAA,EACJ;AAEA,MAAI,CAAC,UAAU;AACX,QAAI,MAAM,SAAS,GAAG;AAClB,cAAQ,KAAK,4DAA4D,OAAO,MAAM,QAAQ;AAAA,IAClG;AACA,WAAO,EAAE,MAAM,UAAU,OAAO,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,KAAK;AAAA,EAChE;AAGA,QAAM,SAAS,MAAM,IAAI,CAAC,MAAM,UAAU;AACtC,QAAI;AACA,aAAO,SAAU,MAAM,KAAK;AAAA,IAChC,SAAS,OAAO;AACZ,cAAQ,MAAM,yCAAyC,KAAK,KAAK,KAAK;AAEtE,aAAO;AAAA,QACH,MAAM;AAAA,QACN,OAAO;AAAA,UACH,UAAU,CAAC,sBAAsB;AAAA,UACjC,OAAO;AAAA,QACX;AAAA,QACA,KAAK;AAAA,MACT;AAAA,IACJ;AAAA,EACJ,CAAC;AAED,SAAO,EAAE,MAAM,UAAU,OAAO,EAAE,UAAU,OAAO,GAAG,KAAK,KAAK;AACpE;AApDgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,33 @@
1
+ import { nixComputed } from "./nixComputed.js";
2
+ import { nixState } from "./nixState.js";
3
+ type ValidationRule<T> = {
4
+ required?: boolean;
5
+ minLength?: number;
6
+ maxLength?: number;
7
+ pattern?: RegExp;
8
+ custom?: (value: any, values: T) => boolean;
9
+ message?: string;
10
+ };
11
+ type ValidationRules<T> = {
12
+ [K in keyof T]?: ValidationRule<T>;
13
+ };
14
+ type FormState<T> = {
15
+ values: ReturnType<typeof nixState<T>>;
16
+ errors: ReturnType<typeof nixState<Partial<Record<keyof T, string>>>>;
17
+ touched: ReturnType<typeof nixState<Partial<Record<keyof T, boolean>>>>;
18
+ isSubmitting: ReturnType<typeof nixState<boolean>>;
19
+ isValid: ReturnType<typeof nixComputed<boolean>>;
20
+ handleChange: (fieldName: keyof T, value: any) => void;
21
+ handleBlur: (fieldName: keyof T) => void;
22
+ handleSubmit: (onSubmit: (values: T, signal: AbortSignal) => Promise<void>) => Promise<void>;
23
+ cancelSubmit: () => void;
24
+ reset: () => void;
25
+ getFieldProps: (fieldName: keyof T) => {
26
+ value: any;
27
+ "r-input": (e: any) => void;
28
+ "r-blur": () => void;
29
+ };
30
+ };
31
+ export declare function nixForm<T extends Record<string, any> = Record<string, any>>(initialValues?: T, validationRules?: ValidationRules<T>): FormState<T>;
32
+ export {};
33
+ //# sourceMappingURL=nixForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nixForm.d.ts","sourceRoot":"","sources":["../../hooks/nixForm.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,KAAK,cAAc,CAAC,CAAC,IAAI;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,eAAe,CAAC,CAAC,IAAI;KACvB,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;CACnC,CAAC;AAEF,KAAK,SAAS,CAAC,CAAC,IAAI;IAClB,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,OAAO,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,YAAY,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,OAAO,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACvD,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CACZ,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,KACxD,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK;QACrC,KAAK,EAAE,GAAG,CAAC;QACX,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;QAC5B,QAAQ,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC;CACH,CAAC;AAgBF,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzE,aAAa,GAAE,CAAW,EAC1B,eAAe,GAAE,eAAe,CAAC,CAAC,CAA4B,GAC7D,SAAS,CAAC,CAAC,CAAC,CA2Id"}
@@ -0,0 +1,123 @@
1
+ import { nixComputed } from "./nixComputed.js";
2
+ import { nixState } from "./nixState.js";
3
+ export function nixForm(initialValues = {}, validationRules = {}) {
4
+ const values = nixState({ ...initialValues });
5
+ const errors = nixState({});
6
+ const touched = nixState({});
7
+ const isSubmitting = nixState(false);
8
+ const isValid = nixComputed(() => Object.keys(errors.value).length === 0);
9
+ let abortController = null;
10
+ function validate(fieldName, value) {
11
+ const rules = validationRules[fieldName];
12
+ if (!rules)
13
+ return null;
14
+ if (rules.required && !value) {
15
+ return rules.message || `${String(fieldName)} is required`;
16
+ }
17
+ if (rules.minLength && value.length < rules.minLength) {
18
+ return (rules.message ||
19
+ `${String(fieldName)} must be at least ${rules.minLength} characters`);
20
+ }
21
+ if (rules.maxLength && value.length > rules.maxLength) {
22
+ return (rules.message ||
23
+ `${String(fieldName)} must be at most ${rules.maxLength} characters`);
24
+ }
25
+ if (rules.pattern && !rules.pattern.test(value)) {
26
+ return rules.message || `${String(fieldName)} is invalid`;
27
+ }
28
+ if (rules.custom && !rules.custom(value, values.value)) {
29
+ return rules.message || `${String(fieldName)} is invalid`;
30
+ }
31
+ return null;
32
+ }
33
+ function handleChange(fieldName, value) {
34
+ values.value = { ...values.value, [fieldName]: value };
35
+ if (touched.value[fieldName]) {
36
+ const error = validate(fieldName, value);
37
+ if (error) {
38
+ errors.value = { ...errors.value, [fieldName]: error };
39
+ }
40
+ else {
41
+ const newErrors = { ...errors.value };
42
+ delete newErrors[fieldName];
43
+ errors.value = newErrors;
44
+ }
45
+ }
46
+ }
47
+ function handleBlur(fieldName) {
48
+ touched.value = { ...touched.value, [fieldName]: true };
49
+ const error = validate(fieldName, values.value[fieldName]);
50
+ if (error) {
51
+ errors.value = { ...errors.value, [fieldName]: error };
52
+ }
53
+ }
54
+ function validateAll() {
55
+ const newErrors = {};
56
+ Object.keys(validationRules).forEach((fieldName) => {
57
+ const error = validate(fieldName, values.value[fieldName]);
58
+ if (error)
59
+ newErrors[fieldName] = error;
60
+ });
61
+ errors.value = newErrors;
62
+ return Object.keys(newErrors).length === 0;
63
+ }
64
+ async function handleSubmit(onSubmit) {
65
+ const allTouched = Object.keys(validationRules).reduce((acc, key) => {
66
+ acc[key] = true;
67
+ return acc;
68
+ }, {});
69
+ touched.value = allTouched;
70
+ if (!validateAll())
71
+ return;
72
+ if (abortController)
73
+ abortController.abort();
74
+ abortController = new AbortController();
75
+ const signal = abortController.signal;
76
+ isSubmitting.value = true;
77
+ try {
78
+ await onSubmit(values.value, signal);
79
+ }
80
+ catch (err) {
81
+ if (err.name !== "AbortError") {
82
+ console.error("[nixForm] Submission error:", err);
83
+ }
84
+ }
85
+ finally {
86
+ if (!signal.aborted)
87
+ isSubmitting.value = false;
88
+ }
89
+ }
90
+ function cancelSubmit() {
91
+ if (abortController) {
92
+ abortController.abort();
93
+ abortController = null;
94
+ }
95
+ }
96
+ function reset() {
97
+ values.value = { ...initialValues };
98
+ errors.value = {};
99
+ touched.value = {};
100
+ isSubmitting.value = false;
101
+ cancelSubmit();
102
+ }
103
+ function getFieldProps(fieldName) {
104
+ return {
105
+ value: values.value[fieldName] || "",
106
+ "r-input": (e) => handleChange(fieldName, e.target.value),
107
+ "r-blur": () => handleBlur(fieldName),
108
+ };
109
+ }
110
+ return {
111
+ values,
112
+ errors,
113
+ touched,
114
+ isSubmitting,
115
+ isValid,
116
+ handleChange,
117
+ handleBlur,
118
+ handleSubmit,
119
+ cancelSubmit,
120
+ reset,
121
+ getFieldProps,
122
+ };
123
+ }