@kupola/kupola 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/INTEGRATION.md +440 -0
  3. package/PROJECT_SUMMARY.md +312 -0
  4. package/README.md +286 -0
  5. package/SKILL.md +572 -0
  6. package/css/accessibility.css +119 -0
  7. package/css/animations.css +224 -0
  8. package/css/brand-themes.css +300 -0
  9. package/css/colors_and_type.css +441 -0
  10. package/css/components-ext.css +4165 -0
  11. package/css/components.css +1483 -0
  12. package/css/kupola.css +12 -0
  13. package/css/responsive.css +697 -0
  14. package/css/scaffold.css +145 -0
  15. package/css/states.css +316 -0
  16. package/css/theme-dark.css +296 -0
  17. package/css/theme-light.css +296 -0
  18. package/css/utilities.css +171 -0
  19. package/dist/css/kupola.css +12 -0
  20. package/dist/icons/icons/activity.svg +3 -0
  21. package/dist/icons/icons/alert-circle.svg +1 -0
  22. package/dist/icons/icons/alert-triangle.svg +1 -0
  23. package/dist/icons/icons/alert.d27e24.svg +1 -0
  24. package/dist/icons/icons/alert.svg +1 -0
  25. package/dist/icons/icons/apple.svg +1 -0
  26. package/dist/icons/icons/arrow-down.svg +1 -0
  27. package/dist/icons/icons/arrow-expand.svg +1 -0
  28. package/dist/icons/icons/arrow-left.svg +1 -0
  29. package/dist/icons/icons/arrow-minimize.svg +1 -0
  30. package/dist/icons/icons/arrow-right-to-line.svg +1 -0
  31. package/dist/icons/icons/arrow-right.svg +1 -0
  32. package/dist/icons/icons/arrow-up-right.svg +1 -0
  33. package/dist/icons/icons/arrow-up.0c0c0d.svg +1 -0
  34. package/dist/icons/icons/arrow-up.svg +1 -0
  35. package/dist/icons/icons/at.svg +1 -0
  36. package/dist/icons/icons/atom.0c0c0d.svg +1 -0
  37. package/dist/icons/icons/atom.svg +1 -0
  38. package/dist/icons/icons/bar-chart-2.svg +1 -0
  39. package/dist/icons/icons/bar.svg +1 -0
  40. package/dist/icons/icons/bell.svg +1 -0
  41. package/dist/icons/icons/box.svg +1 -0
  42. package/dist/icons/icons/bug.svg +1 -0
  43. package/dist/icons/icons/button.svg +1 -0
  44. package/dist/icons/icons/calendar.svg +1 -0
  45. package/dist/icons/icons/check-circle.33c192.svg +1 -0
  46. package/dist/icons/icons/check-circle.svg +1 -0
  47. package/dist/icons/icons/check-square.svg +4 -0
  48. package/dist/icons/icons/check.d1d3db.svg +1 -0
  49. package/dist/icons/icons/check.svg +1 -0
  50. package/dist/icons/icons/chevron-down.svg +1 -0
  51. package/dist/icons/icons/chevron-left.svg +1 -0
  52. package/dist/icons/icons/chevron-right.svg +1 -0
  53. package/dist/icons/icons/chevron-up.svg +1 -0
  54. package/dist/icons/icons/circle.svg +1 -0
  55. package/dist/icons/icons/clock.svg +1 -0
  56. package/dist/icons/icons/cmd.svg +1 -0
  57. package/dist/icons/icons/code.svg +1 -0
  58. package/dist/icons/icons/columns.svg +1 -0
  59. package/dist/icons/icons/copy.svg +1 -0
  60. package/dist/icons/icons/cpu.svg +1 -0
  61. package/dist/icons/icons/dashboard.0c0c0d.svg +1 -0
  62. package/dist/icons/icons/dashboard.svg +1 -0
  63. package/dist/icons/icons/dollar.svg +1 -0
  64. package/dist/icons/icons/download.svg +1 -0
  65. package/dist/icons/icons/edit.svg +1 -0
  66. package/dist/icons/icons/extensions.svg +1 -0
  67. package/dist/icons/icons/external.svg +1 -0
  68. package/dist/icons/icons/eye.svg +1 -0
  69. package/dist/icons/icons/file-text.svg +1 -0
  70. package/dist/icons/icons/file.svg +1 -0
  71. package/dist/icons/icons/files.svg +1 -0
  72. package/dist/icons/icons/filter.svg +1 -0
  73. package/dist/icons/icons/folder-open.svg +4 -0
  74. package/dist/icons/icons/folder.svg +1 -0
  75. package/dist/icons/icons/gear.svg +1 -0
  76. package/dist/icons/icons/git.svg +1 -0
  77. package/dist/icons/icons/github.svg +1 -0
  78. package/dist/icons/icons/globe.svg +1 -0
  79. package/dist/icons/icons/grid-2x2.svg +1 -0
  80. package/dist/icons/icons/hash.svg +1 -0
  81. package/dist/icons/icons/heart.svg +1 -0
  82. package/dist/icons/icons/help-circle.svg +5 -0
  83. package/dist/icons/icons/help.svg +1 -0
  84. package/dist/icons/icons/home.svg +1 -0
  85. package/dist/icons/icons/icons.svg +153 -0
  86. package/dist/icons/icons/image.svg +1 -0
  87. package/dist/icons/icons/info-circle.svg +1 -0
  88. package/dist/icons/icons/info-square.svg +1 -0
  89. package/dist/icons/icons/info.387bff.svg +1 -0
  90. package/dist/icons/icons/info.svg +1 -0
  91. package/dist/icons/icons/input.svg +1 -0
  92. package/dist/icons/icons/key.svg +1 -0
  93. package/dist/icons/icons/layers.svg +1 -0
  94. package/dist/icons/icons/layout-grid.svg +6 -0
  95. package/dist/icons/icons/layout.svg +1 -0
  96. package/dist/icons/icons/link.svg +1 -0
  97. package/dist/icons/icons/list.svg +1 -0
  98. package/dist/icons/icons/lock.svg +1 -0
  99. package/dist/icons/icons/log-out.svg +1 -0
  100. package/dist/icons/icons/logo.svg +1 -0
  101. package/dist/icons/icons/mail.svg +1 -0
  102. package/dist/icons/icons/menu.svg +1 -0
  103. package/dist/icons/icons/message-circle.svg +1 -0
  104. package/dist/icons/icons/message-plus.svg +1 -0
  105. package/dist/icons/icons/message-square.svg +1 -0
  106. package/dist/icons/icons/mic.svg +1 -0
  107. package/dist/icons/icons/minus.svg +1 -0
  108. package/dist/icons/icons/moon.svg +1 -0
  109. package/dist/icons/icons/more-h.svg +1 -0
  110. package/dist/icons/icons/mouse.svg +1 -0
  111. package/dist/icons/icons/palette.svg +1 -0
  112. package/dist/icons/icons/panel-bottom.svg +1 -0
  113. package/dist/icons/icons/pause.svg +1 -0
  114. package/dist/icons/icons/play.svg +1 -0
  115. package/dist/icons/icons/plug.svg +1 -0
  116. package/dist/icons/icons/plus.0c0c0d.svg +1 -0
  117. package/dist/icons/icons/plus.svg +1 -0
  118. package/dist/icons/icons/refresh.svg +1 -0
  119. package/dist/icons/icons/ruler.svg +1 -0
  120. package/dist/icons/icons/scroll-text.svg +1 -0
  121. package/dist/icons/icons/search-menu.svg +1 -0
  122. package/dist/icons/icons/search.svg +1 -0
  123. package/dist/icons/icons/send.0c0c0d.svg +1 -0
  124. package/dist/icons/icons/send.svg +1 -0
  125. package/dist/icons/icons/settings.0c0c0d.svg +1 -0
  126. package/dist/icons/icons/settings.svg +1 -0
  127. package/dist/icons/icons/shield.svg +1 -0
  128. package/dist/icons/icons/sidebar-left.svg +1 -0
  129. package/dist/icons/icons/sidebar-right.svg +1 -0
  130. package/dist/icons/icons/sliders.svg +1 -0
  131. package/dist/icons/icons/sparkles.32f08c.svg +1 -0
  132. package/dist/icons/icons/sparkles.svg +1 -0
  133. package/dist/icons/icons/square.svg +1 -0
  134. package/dist/icons/icons/star.svg +1 -0
  135. package/dist/icons/icons/sun.svg +1 -0
  136. package/dist/icons/icons/table.svg +1 -0
  137. package/dist/icons/icons/terminal.svg +1 -0
  138. package/dist/icons/icons/trash.svg +1 -0
  139. package/dist/icons/icons/trending-down.svg +1 -0
  140. package/dist/icons/icons/trending-up.svg +1 -0
  141. package/dist/icons/icons/type.svg +1 -0
  142. package/dist/icons/icons/upload.svg +1 -0
  143. package/dist/icons/icons/user-circle.svg +1 -0
  144. package/dist/icons/icons/user-plus.svg +6 -0
  145. package/dist/icons/icons/user.svg +1 -0
  146. package/dist/icons/icons/users.svg +1 -0
  147. package/dist/icons/icons/warning.svg +1 -0
  148. package/dist/icons/icons/wrench.svg +1 -0
  149. package/dist/icons/icons/x-circle.svg +1 -0
  150. package/dist/icons/icons/x.svg +1 -0
  151. package/dist/icons/icons/zap.0c0c0d.svg +1 -0
  152. package/dist/icons/icons/zap.svg +1 -0
  153. package/dist/kupola.cjs.js +258 -0
  154. package/dist/kupola.cjs.js.map +1 -0
  155. package/dist/kupola.esm.js +7640 -0
  156. package/dist/kupola.esm.js.map +1 -0
  157. package/dist/kupola.umd.js +258 -0
  158. package/dist/kupola.umd.js.map +1 -0
  159. package/dist/types/kupola.d.ts +1359 -0
  160. package/dist/utils/utils/Kupola.cs +77 -0
  161. package/dist/utils/utils/Kupola.java +104 -0
  162. package/dist/utils/utils/kupola.go +120 -0
  163. package/dist/utils/utils/kupola.js +63 -0
  164. package/dist/utils/utils/kupola.py +1392 -0
  165. package/dist/utils/utils/kupola.rb +69 -0
  166. package/icons/activity.svg +3 -0
  167. package/icons/alert-circle.svg +1 -0
  168. package/icons/alert-triangle.svg +1 -0
  169. package/icons/alert.d27e24.svg +1 -0
  170. package/icons/alert.svg +1 -0
  171. package/icons/apple.svg +1 -0
  172. package/icons/arrow-down.svg +1 -0
  173. package/icons/arrow-expand.svg +1 -0
  174. package/icons/arrow-left.svg +1 -0
  175. package/icons/arrow-minimize.svg +1 -0
  176. package/icons/arrow-right-to-line.svg +1 -0
  177. package/icons/arrow-right.svg +1 -0
  178. package/icons/arrow-up-right.svg +1 -0
  179. package/icons/arrow-up.0c0c0d.svg +1 -0
  180. package/icons/arrow-up.svg +1 -0
  181. package/icons/at.svg +1 -0
  182. package/icons/atom.0c0c0d.svg +1 -0
  183. package/icons/atom.svg +1 -0
  184. package/icons/bar-chart-2.svg +1 -0
  185. package/icons/bar.svg +1 -0
  186. package/icons/bell.svg +1 -0
  187. package/icons/box.svg +1 -0
  188. package/icons/bug.svg +1 -0
  189. package/icons/button.svg +1 -0
  190. package/icons/calendar.svg +1 -0
  191. package/icons/check-circle.33c192.svg +1 -0
  192. package/icons/check-circle.svg +1 -0
  193. package/icons/check-square.svg +4 -0
  194. package/icons/check.d1d3db.svg +1 -0
  195. package/icons/check.svg +1 -0
  196. package/icons/chevron-down.svg +1 -0
  197. package/icons/chevron-left.svg +1 -0
  198. package/icons/chevron-right.svg +1 -0
  199. package/icons/chevron-up.svg +1 -0
  200. package/icons/circle.svg +1 -0
  201. package/icons/clock.svg +1 -0
  202. package/icons/cmd.svg +1 -0
  203. package/icons/code.svg +1 -0
  204. package/icons/columns.svg +1 -0
  205. package/icons/copy.svg +1 -0
  206. package/icons/cpu.svg +1 -0
  207. package/icons/dashboard.0c0c0d.svg +1 -0
  208. package/icons/dashboard.svg +1 -0
  209. package/icons/dollar.svg +1 -0
  210. package/icons/download.svg +1 -0
  211. package/icons/edit.svg +1 -0
  212. package/icons/extensions.svg +1 -0
  213. package/icons/external.svg +1 -0
  214. package/icons/eye.svg +1 -0
  215. package/icons/file-text.svg +1 -0
  216. package/icons/file.svg +1 -0
  217. package/icons/files.svg +1 -0
  218. package/icons/filter.svg +1 -0
  219. package/icons/folder-open.svg +4 -0
  220. package/icons/folder.svg +1 -0
  221. package/icons/gear.svg +1 -0
  222. package/icons/git.svg +1 -0
  223. package/icons/github.svg +1 -0
  224. package/icons/globe.svg +1 -0
  225. package/icons/grid-2x2.svg +1 -0
  226. package/icons/hash.svg +1 -0
  227. package/icons/heart.svg +1 -0
  228. package/icons/help-circle.svg +5 -0
  229. package/icons/help.svg +1 -0
  230. package/icons/home.svg +1 -0
  231. package/icons/icons.svg +153 -0
  232. package/icons/image.svg +1 -0
  233. package/icons/info-circle.svg +1 -0
  234. package/icons/info-square.svg +1 -0
  235. package/icons/info.387bff.svg +1 -0
  236. package/icons/info.svg +1 -0
  237. package/icons/input.svg +1 -0
  238. package/icons/key.svg +1 -0
  239. package/icons/layers.svg +1 -0
  240. package/icons/layout-grid.svg +6 -0
  241. package/icons/layout.svg +1 -0
  242. package/icons/link.svg +1 -0
  243. package/icons/list.svg +1 -0
  244. package/icons/lock.svg +1 -0
  245. package/icons/log-out.svg +1 -0
  246. package/icons/logo.svg +1 -0
  247. package/icons/mail.svg +1 -0
  248. package/icons/menu.svg +1 -0
  249. package/icons/message-circle.svg +1 -0
  250. package/icons/message-plus.svg +1 -0
  251. package/icons/message-square.svg +1 -0
  252. package/icons/mic.svg +1 -0
  253. package/icons/minus.svg +1 -0
  254. package/icons/moon.svg +1 -0
  255. package/icons/more-h.svg +1 -0
  256. package/icons/mouse.svg +1 -0
  257. package/icons/palette.svg +1 -0
  258. package/icons/panel-bottom.svg +1 -0
  259. package/icons/pause.svg +1 -0
  260. package/icons/play.svg +1 -0
  261. package/icons/plug.svg +1 -0
  262. package/icons/plus.0c0c0d.svg +1 -0
  263. package/icons/plus.svg +1 -0
  264. package/icons/refresh.svg +1 -0
  265. package/icons/ruler.svg +1 -0
  266. package/icons/scroll-text.svg +1 -0
  267. package/icons/search-menu.svg +1 -0
  268. package/icons/search.svg +1 -0
  269. package/icons/send.0c0c0d.svg +1 -0
  270. package/icons/send.svg +1 -0
  271. package/icons/settings.0c0c0d.svg +1 -0
  272. package/icons/settings.svg +1 -0
  273. package/icons/shield.svg +1 -0
  274. package/icons/sidebar-left.svg +1 -0
  275. package/icons/sidebar-right.svg +1 -0
  276. package/icons/sliders.svg +1 -0
  277. package/icons/sparkles.32f08c.svg +1 -0
  278. package/icons/sparkles.svg +1 -0
  279. package/icons/square.svg +1 -0
  280. package/icons/star.svg +1 -0
  281. package/icons/sun.svg +1 -0
  282. package/icons/table.svg +1 -0
  283. package/icons/terminal.svg +1 -0
  284. package/icons/trash.svg +1 -0
  285. package/icons/trending-down.svg +1 -0
  286. package/icons/trending-up.svg +1 -0
  287. package/icons/type.svg +1 -0
  288. package/icons/upload.svg +1 -0
  289. package/icons/user-circle.svg +1 -0
  290. package/icons/user-plus.svg +6 -0
  291. package/icons/user.svg +1 -0
  292. package/icons/users.svg +1 -0
  293. package/icons/warning.svg +1 -0
  294. package/icons/wrench.svg +1 -0
  295. package/icons/x-circle.svg +1 -0
  296. package/icons/x.svg +1 -0
  297. package/icons/zap.0c0c0d.svg +1 -0
  298. package/icons/zap.svg +1 -0
  299. package/js/calendar.js +184 -0
  300. package/js/carousel.js +173 -0
  301. package/js/collapse.js +127 -0
  302. package/js/color-picker.js +144 -0
  303. package/js/component.js +276 -0
  304. package/js/composition-api.js +458 -0
  305. package/js/countdown.js +144 -0
  306. package/js/data-bind.js +1286 -0
  307. package/js/datepicker.js +270 -0
  308. package/js/dialog.js +131 -0
  309. package/js/drawer.js +118 -0
  310. package/js/dropdown.js +122 -0
  311. package/js/dynamic-tags.js +156 -0
  312. package/js/error-handler.js +181 -0
  313. package/js/fileupload.js +392 -0
  314. package/js/form.js +291 -0
  315. package/js/global-events.js +203 -0
  316. package/js/heatmap.js +405 -0
  317. package/js/http.js +419 -0
  318. package/js/i18n.js +228 -0
  319. package/js/icons.js +165 -0
  320. package/js/image-preview.js +218 -0
  321. package/js/initializer.js +162 -0
  322. package/js/kupola-core.js +66 -0
  323. package/js/kupola-devtools.js +598 -0
  324. package/js/kupola-lifecycle.js +510 -0
  325. package/js/message.js +60 -0
  326. package/js/modal.js +191 -0
  327. package/js/notification.js +73 -0
  328. package/js/numberinput.js +146 -0
  329. package/js/performance.js +250 -0
  330. package/js/registry.js +205 -0
  331. package/js/router.js +396 -0
  332. package/js/security.js +189 -0
  333. package/js/select.js +143 -0
  334. package/js/slide-captcha.js +504 -0
  335. package/js/slider.js +94 -0
  336. package/js/statcard.js +219 -0
  337. package/js/tag.js +83 -0
  338. package/js/test-utils.js +251 -0
  339. package/js/theme.js +363 -0
  340. package/js/timepicker.js +238 -0
  341. package/js/tooltip.js +122 -0
  342. package/js/utils.js +1458 -0
  343. package/js/validation.js +553 -0
  344. package/js/virtual-list.js +593 -0
  345. package/package.json +105 -0
  346. package/templates/base.html +30 -0
  347. package/templates/base_dashboard.html +99 -0
  348. package/types/kupola.d.ts +1359 -0
  349. package/utils/Kupola.cs +77 -0
  350. package/utils/Kupola.java +104 -0
  351. package/utils/kupola.go +120 -0
  352. package/utils/kupola.js +63 -0
  353. package/utils/kupola.py +1392 -0
  354. package/utils/kupola.rb +69 -0
@@ -0,0 +1,1286 @@
1
+ class TrieNode {
2
+ constructor() {
3
+ this.children = {};
4
+ this.keys = [];
5
+ }
6
+ }
7
+
8
+ class PathTrie {
9
+ constructor() {
10
+ this.root = new TrieNode();
11
+ }
12
+
13
+ insert(key) {
14
+ let node = this.root;
15
+ const parts = key.split('.');
16
+
17
+ parts.forEach((part, index) => {
18
+ if (!node.children[part]) {
19
+ node.children[part] = new TrieNode();
20
+ }
21
+ node = node.children[part];
22
+ if (index === parts.length - 1) {
23
+ node.keys.push(key);
24
+ }
25
+ });
26
+ }
27
+
28
+ getSubKeys(key) {
29
+ let node = this.root;
30
+ const parts = key.split('.');
31
+ const result = [];
32
+
33
+ for (let i = 0; i < parts.length; i++) {
34
+ const part = parts[i];
35
+ if (!node.children[part]) {
36
+ break;
37
+ }
38
+ node = node.children[part];
39
+
40
+ const collectKeys = (n) => {
41
+ if (n.keys.length > 0) {
42
+ result.push(...n.keys);
43
+ }
44
+ Object.values(n.children).forEach(child => collectKeys(child));
45
+ };
46
+ collectKeys(node);
47
+ }
48
+
49
+ return [...new Set(result)];
50
+ }
51
+
52
+ getParentKeys(key) {
53
+ const parts = key.split('.');
54
+ const result = [];
55
+
56
+ for (let i = 1; i <= parts.length; i++) {
57
+ const parentPath = parts.slice(0, i).join('.');
58
+ result.push(parentPath);
59
+ }
60
+
61
+ return result;
62
+ }
63
+ }
64
+
65
+ const REACTIVE_SYMBOLS = {
66
+ parent: Symbol('reactive_parent'),
67
+ path: Symbol('reactive_path'),
68
+ isReactive: Symbol('reactive_is_reactive')
69
+ };
70
+
71
+ class KupolaDataBind {
72
+ constructor() {
73
+ this.rawData = {};
74
+ this.data = null;
75
+ this.observers = {};
76
+ this.elements = {};
77
+ this.computedProperties = {};
78
+ this.pathTrie = new PathTrie();
79
+ this.updateQueue = new Map();
80
+ this.isProcessing = false;
81
+ this.pendingComputed = new Set();
82
+ this.persistedKeys = new Map();
83
+ this.snapshots = [];
84
+ this.snapshotLimit = 10;
85
+ this._proxyCache = new WeakMap();
86
+
87
+ this.createReactiveData();
88
+ }
89
+
90
+ createReactiveData() {
91
+ const handler = {
92
+ get: (target, key, receiver) => {
93
+ if (key === '__raw__') return target;
94
+ const result = Reflect.get(target, key, receiver);
95
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
96
+ return this.wrapReactive(result, key);
97
+ }
98
+ return result;
99
+ },
100
+ set: (target, key, value, receiver) => {
101
+ const oldValue = Reflect.get(target, key, receiver);
102
+ const result = Reflect.set(target, key, value, receiver);
103
+
104
+ const fullPath = this.resolvePath(target, key);
105
+ this.notify(fullPath, value, oldValue);
106
+ this.queueUpdate(fullPath, value);
107
+
108
+ return result;
109
+ },
110
+ deleteProperty: (target, key) => {
111
+ const oldValue = Reflect.get(target, key, receiver);
112
+ const result = Reflect.deleteProperty(target, key);
113
+
114
+ const fullPath = this.resolvePath(target, key);
115
+ this.notify(fullPath, undefined, oldValue);
116
+ this.queueUpdate(fullPath, undefined);
117
+
118
+ return result;
119
+ }
120
+ };
121
+
122
+ this.data = new Proxy(this.rawData, handler);
123
+ this.data.__parent__ = null;
124
+ this.data.__path__ = '';
125
+ }
126
+
127
+ wrapReactive(obj, parentKey) {
128
+ if (obj[REACTIVE_SYMBOLS.isReactive]) return obj;
129
+
130
+ if (this._proxyCache.has(obj)) {
131
+ return this._proxyCache.get(obj);
132
+ }
133
+
134
+ const handler = {
135
+ get: (target, key, receiver) => {
136
+ if (key === '__raw__') return target;
137
+ if (key === REACTIVE_SYMBOLS.parent || key === '__parent__') return target[REACTIVE_SYMBOLS.parent];
138
+ if (key === REACTIVE_SYMBOLS.path || key === '__path__') return target[REACTIVE_SYMBOLS.path];
139
+ if (key === REACTIVE_SYMBOLS.isReactive || key === '__isReactive__') return true;
140
+
141
+ const result = Reflect.get(target, key, receiver);
142
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
143
+ return this.wrapReactive(result, `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? '.' : ''}${key}`);
144
+ }
145
+ return result;
146
+ },
147
+ set: (target, key, value, receiver) => {
148
+ if (key === REACTIVE_SYMBOLS.parent ||
149
+ key === REACTIVE_SYMBOLS.path ||
150
+ key === REACTIVE_SYMBOLS.isReactive ||
151
+ key === '__parent__' ||
152
+ key === '__path__' ||
153
+ key === '__isReactive__') {
154
+ return true;
155
+ }
156
+
157
+ const oldValue = Reflect.get(target, key, receiver);
158
+ const result = Reflect.set(target, key, value, receiver);
159
+
160
+ const fullPath = `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? '.' : ''}${key}`;
161
+ this.notify(fullPath, value, oldValue);
162
+ this.queueUpdate(fullPath, value);
163
+
164
+ return result;
165
+ },
166
+ deleteProperty: (target, key) => {
167
+ if (key === REACTIVE_SYMBOLS.parent ||
168
+ key === REACTIVE_SYMBOLS.path ||
169
+ key === REACTIVE_SYMBOLS.isReactive) {
170
+ return false;
171
+ }
172
+
173
+ const oldValue = Reflect.get(target, key);
174
+ const result = Reflect.deleteProperty(target, key);
175
+
176
+ const fullPath = `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? '.' : ''}${key}`;
177
+ this.notify(fullPath, undefined, oldValue);
178
+ this.queueUpdate(fullPath, undefined);
179
+
180
+ return result;
181
+ },
182
+ has: (target, key) => {
183
+ if (key === '__raw__' ||
184
+ key === REACTIVE_SYMBOLS.parent ||
185
+ key === REACTIVE_SYMBOLS.path ||
186
+ key === REACTIVE_SYMBOLS.isReactive ||
187
+ key === '__parent__' ||
188
+ key === '__path__' ||
189
+ key === '__isReactive__') {
190
+ return true;
191
+ }
192
+ return key in target;
193
+ },
194
+ ownKeys: (target) => {
195
+ return Reflect.ownKeys(target).filter(key =>
196
+ key !== REACTIVE_SYMBOLS.parent &&
197
+ key !== REACTIVE_SYMBOLS.path &&
198
+ key !== REACTIVE_SYMBOLS.isReactive
199
+ );
200
+ },
201
+ getOwnPropertyDescriptor: (target, key) => {
202
+ if (key === REACTIVE_SYMBOLS.parent ||
203
+ key === REACTIVE_SYMBOLS.path ||
204
+ key === REACTIVE_SYMBOLS.isReactive) {
205
+ return {
206
+ configurable: false,
207
+ enumerable: false,
208
+ writable: false,
209
+ value: target[key]
210
+ };
211
+ }
212
+ return Reflect.getOwnPropertyDescriptor(target, key);
213
+ }
214
+ };
215
+
216
+ const proxy = new Proxy(obj, handler);
217
+ obj[REACTIVE_SYMBOLS.parent] = obj;
218
+ obj[REACTIVE_SYMBOLS.path] = parentKey;
219
+ obj[REACTIVE_SYMBOLS.isReactive] = true;
220
+ this._proxyCache.set(obj, proxy);
221
+
222
+ Object.keys(obj).forEach(key => {
223
+ if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
224
+ obj[key] = this.wrapReactive(obj[key], `${parentKey}${parentKey ? '.' : ''}${key}`);
225
+ }
226
+ });
227
+
228
+ return proxy;
229
+ }
230
+
231
+ resolvePath(target, key) {
232
+ if (target[REACTIVE_SYMBOLS.path]) {
233
+ return `${target[REACTIVE_SYMBOLS.path]}.${key}`;
234
+ }
235
+ return key;
236
+ }
237
+
238
+ queueUpdate(key, value) {
239
+ this.updateQueue.set(key, value);
240
+
241
+ if (!this.isProcessing) {
242
+ this.isProcessing = true;
243
+ requestAnimationFrame(() => {
244
+ this.processQueue();
245
+ });
246
+ }
247
+ }
248
+
249
+ processQueue() {
250
+ const updatedKeys = new Set();
251
+
252
+ this.updateQueue.forEach((value, key) => {
253
+ updatedKeys.add(key);
254
+ this.updateElementsDirect(key, value);
255
+
256
+ const subKeys = this.pathTrie.getSubKeys(key);
257
+ subKeys.forEach(subKey => {
258
+ if (!updatedKeys.has(subKey)) {
259
+ const subValue = this.get(subKey);
260
+ this.updateElementsDirect(subKey, subValue);
261
+ updatedKeys.add(subKey);
262
+ }
263
+ });
264
+ });
265
+
266
+ this.updateQueue.clear();
267
+ this.processComputed();
268
+ this.isProcessing = false;
269
+ }
270
+
271
+ updateElementsDirect(key, value) {
272
+ if (this.elements[key]) {
273
+ this.elements[key].forEach(element => {
274
+ this.updateElement(element, value);
275
+ });
276
+ }
277
+ }
278
+
279
+ processComputed() {
280
+ const computedKeys = Object.keys(this.computedProperties);
281
+
282
+ computedKeys.forEach(name => {
283
+ const prop = this.computedProperties[name];
284
+ const depsChanged = prop.deps.some(dep => {
285
+ return this.updateQueue.has(dep) ||
286
+ this.pathTrie.getSubKeys(dep).some(k => this.updateQueue.has(k));
287
+ });
288
+
289
+ if (depsChanged) {
290
+ this.updateComputedProperty(name);
291
+ }
292
+ });
293
+ }
294
+
295
+ set(key, value, silent = false) {
296
+ const oldValue = this.get(key);
297
+
298
+ if (typeof key === 'object') {
299
+ Object.assign(this.rawData, key);
300
+ Object.keys(key).forEach(k => {
301
+ if (!silent) {
302
+ this.notify(k, key[k], oldValue?.[k]);
303
+ this.queueUpdate(k, key[k]);
304
+ }
305
+ });
306
+ } else {
307
+ if (key.includes('.')) {
308
+ this.setNested(key, value);
309
+ } else {
310
+ this.rawData[key] = value;
311
+ }
312
+
313
+ if (!silent) {
314
+ this.notify(key, value, oldValue);
315
+ this.queueUpdate(key, value);
316
+ }
317
+ }
318
+
319
+ if (!silent) {
320
+ this.processComputed();
321
+ }
322
+ }
323
+
324
+ get(key) {
325
+ if (!key) return undefined;
326
+ if (key.includes('.')) {
327
+ return this.getNested(key);
328
+ }
329
+ return this.rawData[key];
330
+ }
331
+
332
+ getNested(path) {
333
+ if (!path) return undefined;
334
+ return path.split('.').reduce((obj, key) => obj?.[key], this.rawData);
335
+ }
336
+
337
+ setNested(path, value) {
338
+ const keys = path.split('.');
339
+ const lastKey = keys.pop();
340
+ const parent = keys.reduce((obj, key) => {
341
+ if (!obj[key]) obj[key] = {};
342
+ return obj[key];
343
+ }, this.rawData);
344
+
345
+ const oldValue = parent[lastKey];
346
+ parent[lastKey] = value;
347
+ this.notify(path, value, oldValue);
348
+ this.queueUpdate(path, value);
349
+ }
350
+
351
+ observe(key, callback) {
352
+ if (!this.observers[key]) {
353
+ this.observers[key] = [];
354
+ }
355
+ this.observers[key].push(callback);
356
+ }
357
+
358
+ unobserve(key, callback) {
359
+ if (this.observers[key]) {
360
+ this.observers[key] = this.observers[key].filter(cb => cb !== callback);
361
+ }
362
+ }
363
+
364
+ notify(key, value, oldValue) {
365
+ if (this.observers[key]) {
366
+ this.observers[key].forEach(callback => {
367
+ try {
368
+ callback(value, oldValue);
369
+ } catch (e) {
370
+ console.error(`Observer error for ${key}:`, e);
371
+ }
372
+ });
373
+ }
374
+
375
+ this.observers['*']?.forEach(callback => {
376
+ try {
377
+ callback(key, value, oldValue);
378
+ } catch (e) {
379
+ console.error('Wildcard observer error:', e);
380
+ }
381
+ });
382
+ }
383
+
384
+ updateElement(element, value) {
385
+ const binding = element.getAttribute('data-bind');
386
+ if (!binding) return;
387
+
388
+ const bindings = binding.split('|');
389
+
390
+ bindings.forEach(b => {
391
+ const parts = b.split(':');
392
+ const type = parts[0].trim();
393
+ const target = parts[1]?.trim();
394
+
395
+ switch (type) {
396
+ case 'text':
397
+ if (element.textContent !== String(value ?? '')) {
398
+ element.textContent = value ?? '';
399
+ }
400
+ break;
401
+ case 'html':
402
+ if (element.innerHTML !== String(value ?? '')) {
403
+ element.innerHTML = value ?? '';
404
+ }
405
+ break;
406
+ case 'value':
407
+ if (element.type === 'checkbox') {
408
+ if (element.checked !== !!value) {
409
+ element.checked = !!value;
410
+ }
411
+ } else if (element.value !== String(value ?? '')) {
412
+ element.value = value ?? '';
413
+ }
414
+ break;
415
+ case 'checked':
416
+ if (element.checked !== !!value) {
417
+ element.checked = !!value;
418
+ }
419
+ break;
420
+ case 'disabled':
421
+ if (element.disabled !== !!value) {
422
+ element.disabled = !!value;
423
+ }
424
+ break;
425
+ case 'hidden':
426
+ const display = value ? 'none' : '';
427
+ if (element.style.display !== display) {
428
+ element.style.display = display;
429
+ }
430
+ break;
431
+ case 'class':
432
+ if (target) {
433
+ if (value) {
434
+ element.classList.add(target);
435
+ } else {
436
+ element.classList.remove(target);
437
+ }
438
+ }
439
+ break;
440
+ case 'style':
441
+ if (target && element.style[target] !== String(value ?? '')) {
442
+ element.style[target] = value ?? '';
443
+ }
444
+ break;
445
+ case 'attr':
446
+ if (target) {
447
+ const current = element.getAttribute(target);
448
+ if (current !== String(value ?? '')) {
449
+ element.setAttribute(target, value ?? '');
450
+ }
451
+ }
452
+ break;
453
+ case 'src':
454
+ if (element.src !== String(value ?? '')) {
455
+ element.src = value ?? '';
456
+ }
457
+ break;
458
+ case 'href':
459
+ if (element.href !== String(value ?? '')) {
460
+ element.href = value ?? '';
461
+ }
462
+ break;
463
+ case 'placeholder':
464
+ if (element.placeholder !== String(value ?? '')) {
465
+ element.placeholder = value ?? '';
466
+ }
467
+ break;
468
+ }
469
+ });
470
+ }
471
+
472
+ computed(name, deps, callback) {
473
+ this.computedProperties[name] = { deps, callback };
474
+
475
+ deps.forEach(dep => {
476
+ this.pathTrie.insert(dep);
477
+ });
478
+
479
+ this.updateComputedProperty(name);
480
+ }
481
+
482
+ updateComputedProperty(name) {
483
+ const prop = this.computedProperties[name];
484
+ if (!prop) return;
485
+
486
+ try {
487
+ const values = prop.deps.map(dep => this.get(dep));
488
+ const result = prop.callback(...values);
489
+ this.set(name, result, true);
490
+ } catch (e) {
491
+ console.error(`Computed error for ${name}:`, e);
492
+ }
493
+ }
494
+
495
+ load(data) {
496
+ Object.keys(data).forEach(key => {
497
+ if (data[key] && typeof data[key] === 'object' && !Array.isArray(data[key])) {
498
+ this.rawData[key] = this.wrapReactive(data[key], key);
499
+ } else {
500
+ this.rawData[key] = data[key];
501
+ }
502
+ this.queueUpdate(key, this.rawData[key]);
503
+ });
504
+
505
+ this.processComputed();
506
+ }
507
+
508
+ reset() {
509
+ this.rawData = {};
510
+ this.observers = {};
511
+ this.elements = {};
512
+ this.computedProperties = {};
513
+ this.pathTrie = new PathTrie();
514
+ this.updateQueue.clear();
515
+ this.snapshots = [];
516
+ this.createReactiveData();
517
+ this.bind();
518
+ }
519
+
520
+ persist(key, options = {}) {
521
+ const {
522
+ storage: storageType = 'local',
523
+ debounce = 0,
524
+ version = 1,
525
+ encrypt = false,
526
+ encryptionKey = null
527
+ } = options;
528
+
529
+ const storage = storageType === 'session' ? sessionStorage : localStorage;
530
+
531
+ this.persistedKeys.set(key, {
532
+ storage,
533
+ debounce,
534
+ timeout: null,
535
+ version,
536
+ encrypt,
537
+ encryptionKey
538
+ });
539
+
540
+ const value = this.get(key);
541
+ if (value !== undefined) {
542
+ this._persistSave(key, value, storage, { version, encrypt, encryptionKey });
543
+ }
544
+
545
+ this.observe(key, (newValue) => {
546
+ const config = this.persistedKeys.get(key);
547
+ if (!config) return;
548
+
549
+ if (config.debounce > 0) {
550
+ if (config.timeout) clearTimeout(config.timeout);
551
+ config.timeout = setTimeout(() => {
552
+ this._persistSave(key, newValue, config.storage, {
553
+ version: config.version,
554
+ encrypt: config.encrypt,
555
+ encryptionKey: config.encryptionKey
556
+ });
557
+ }, config.debounce);
558
+ } else {
559
+ this._persistSave(key, newValue, config.storage, {
560
+ version: config.version,
561
+ encrypt: config.encrypt,
562
+ encryptionKey: config.encryptionKey
563
+ });
564
+ }
565
+ });
566
+ }
567
+
568
+ _persistSave(key, value, storage, options = {}) {
569
+ try {
570
+ const { version = 1, encrypt = false, encryptionKey = null } = options;
571
+
572
+ const dataToStore = {
573
+ value,
574
+ version,
575
+ timestamp: Date.now()
576
+ };
577
+
578
+ let serialized = JSON.stringify(dataToStore);
579
+
580
+ if (encrypt && encryptionKey) {
581
+ serialized = this._encrypt(serialized, encryptionKey);
582
+ }
583
+
584
+ this._ensureStorageCapacity(storage);
585
+ storage.setItem(`kupola:${key}`, serialized);
586
+ } catch (e) {
587
+ console.warn(`Failed to persist key ${key}:`, e);
588
+
589
+ if (e.name === 'QuotaExceededError' && storage === localStorage) {
590
+ console.warn(`localStorage quota exceeded, trying sessionStorage for key ${key}`);
591
+ try {
592
+ sessionStorage.setItem(`kupola:${key}`, JSON.stringify({ value, version: 1 }));
593
+ } catch (e2) {
594
+ console.warn(`sessionStorage also failed for key ${key}:`, e2);
595
+ }
596
+ }
597
+ }
598
+ }
599
+
600
+ _ensureStorageCapacity(storage) {
601
+ try {
602
+ const testKey = `kupola:__storage_test__`;
603
+ storage.setItem(testKey, 'test');
604
+ storage.removeItem(testKey);
605
+ } catch (e) {
606
+ if (e.name === 'QuotaExceededError') {
607
+ this._cleanupOldStorage(storage);
608
+ }
609
+ }
610
+ }
611
+
612
+ _cleanupOldStorage(storage) {
613
+ const now = Date.now();
614
+ const maxAge = 30 * 24 * 60 * 60 * 1000;
615
+
616
+ for (let i = 0; i < storage.length; i++) {
617
+ const key = storage.key(i);
618
+ if (key?.startsWith('kupola:')) {
619
+ try {
620
+ const data = JSON.parse(storage.getItem(key));
621
+ if (data.timestamp && now - data.timestamp > maxAge) {
622
+ storage.removeItem(key);
623
+ }
624
+ } catch (e) {
625
+ storage.removeItem(key);
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ _encrypt(text, key) {
632
+ if (!window.CryptoJS) {
633
+ console.warn('CryptoJS not available, encryption skipped');
634
+ return text;
635
+ }
636
+ return window.CryptoJS.AES.encrypt(text, key).toString();
637
+ }
638
+
639
+ _decrypt(ciphertext, key) {
640
+ if (!window.CryptoJS) {
641
+ console.warn('CryptoJS not available, decryption skipped');
642
+ return ciphertext;
643
+ }
644
+ try {
645
+ const bytes = window.CryptoJS.AES.decrypt(ciphertext, key);
646
+ return bytes.toString(window.CryptoJS.enc.Utf8);
647
+ } catch (e) {
648
+ console.warn('Decryption failed:', e);
649
+ return ciphertext;
650
+ }
651
+ }
652
+
653
+ unpersist(key) {
654
+ const config = this.persistedKeys.get(key);
655
+ if (config) {
656
+ if (config.timeout) clearTimeout(config.timeout);
657
+ config.storage.removeItem(`kupola:${key}`);
658
+ this.persistedKeys.delete(key);
659
+ }
660
+ }
661
+
662
+ loadPersisted(options = {}) {
663
+ const persisted = {};
664
+ const { encryptionKey = null } = options;
665
+
666
+ for (let i = 0; i < localStorage.length; i++) {
667
+ const key = localStorage.key(i);
668
+ if (key?.startsWith('kupola:')) {
669
+ const dataKey = key.replace('kupola:', '');
670
+ try {
671
+ const stored = localStorage.getItem(key);
672
+ let parsed;
673
+
674
+ if (encryptionKey) {
675
+ const decrypted = this._decrypt(stored, encryptionKey);
676
+ parsed = JSON.parse(decrypted);
677
+ } else {
678
+ parsed = JSON.parse(stored);
679
+ }
680
+
681
+ if (parsed.version !== undefined && parsed.version !== options.version) {
682
+ console.debug(`Skipping outdated data for ${dataKey} (version ${parsed.version})`);
683
+ continue;
684
+ }
685
+
686
+ persisted[dataKey] = parsed.value !== undefined ? parsed.value : parsed;
687
+ } catch (e) {
688
+ console.warn(`Failed to load persisted key ${dataKey}:`, e);
689
+ }
690
+ }
691
+ }
692
+
693
+ for (let i = 0; i < sessionStorage.length; i++) {
694
+ const key = sessionStorage.key(i);
695
+ if (key?.startsWith('kupola:')) {
696
+ const dataKey = key.replace('kupola:', '');
697
+ try {
698
+ const stored = sessionStorage.getItem(key);
699
+ let parsed;
700
+
701
+ if (encryptionKey) {
702
+ const decrypted = this._decrypt(stored, encryptionKey);
703
+ parsed = JSON.parse(decrypted);
704
+ } else {
705
+ parsed = JSON.parse(stored);
706
+ }
707
+
708
+ if (parsed.version !== undefined && parsed.version !== options.version) {
709
+ console.debug(`Skipping outdated data for ${dataKey} (version ${parsed.version})`);
710
+ continue;
711
+ }
712
+
713
+ persisted[dataKey] = parsed.value !== undefined ? parsed.value : parsed;
714
+ } catch (e) {}
715
+ }
716
+ }
717
+
718
+ if (Object.keys(persisted).length > 0) {
719
+ this.load(persisted);
720
+ }
721
+
722
+ return persisted;
723
+ }
724
+
725
+ _clone(data) {
726
+ if (typeof structuredClone === 'function') {
727
+ try {
728
+ return structuredClone(data);
729
+ } catch (e) {
730
+ console.warn('structuredClone failed, falling back to JSON:', e);
731
+ }
732
+ }
733
+ return JSON.parse(JSON.stringify(data));
734
+ }
735
+
736
+ snapshot() {
737
+ const snapshot = this._clone(this.rawData);
738
+ this.snapshots.push(snapshot);
739
+ if (this.snapshots.length > this.snapshotLimit) {
740
+ this.snapshots.shift();
741
+ }
742
+ return this.snapshots.length - 1;
743
+ }
744
+
745
+ rollback(index = -1) {
746
+ if (this.snapshots.length === 0) return false;
747
+
748
+ const snapshotIndex = index >= 0 ? index : this.snapshots.length - 1;
749
+ const snapshot = this.snapshots[snapshotIndex];
750
+
751
+ if (!snapshot) return false;
752
+
753
+ this.rawData = this._clone(snapshot);
754
+ this.createReactiveData();
755
+
756
+ Object.keys(this.rawData).forEach(key => {
757
+ this.queueUpdate(key, this.rawData[key]);
758
+ });
759
+
760
+ this.processComputed();
761
+ return true;
762
+ }
763
+
764
+ getSnapshotCount() {
765
+ return this.snapshots.length;
766
+ }
767
+
768
+ clearSnapshots() {
769
+ this.snapshots = [];
770
+ }
771
+
772
+ serializeForm(formElement) {
773
+ const data = {};
774
+
775
+ formElement.querySelectorAll('input, select, textarea').forEach(element => {
776
+ const name = element.getAttribute('data-bind');
777
+ if (!name) return;
778
+
779
+ const parts = name.split(':');
780
+ const key = parts[1]?.trim();
781
+ if (!key) return;
782
+
783
+ if (element.type === 'checkbox') {
784
+ if (!data[key]) data[key] = [];
785
+ if (element.checked) {
786
+ data[key].push(element.value);
787
+ }
788
+ } else if (element.type === 'radio') {
789
+ if (element.checked) {
790
+ data[key] = element.value;
791
+ }
792
+ } else {
793
+ data[key] = element.value;
794
+ }
795
+ });
796
+
797
+ return data;
798
+ }
799
+
800
+ fillForm(formElement, data) {
801
+ Object.keys(data).forEach(key => {
802
+ formElement.querySelectorAll('[data-bind*=":' + key + '"]').forEach(element => {
803
+ if (element.type === 'checkbox') {
804
+ element.checked = Array.isArray(data[key])
805
+ ? data[key].includes(element.value)
806
+ : !!data[key];
807
+ } else if (element.type === 'radio') {
808
+ element.checked = element.value === data[key];
809
+ } else {
810
+ element.value = data[key] ?? '';
811
+ }
812
+ });
813
+ });
814
+ }
815
+
816
+ createReactive(target, path = '') {
817
+ if (target[REACTIVE_SYMBOLS.isReactive]) return target;
818
+
819
+ if (this._proxyCache.has(target)) {
820
+ return this._proxyCache.get(target);
821
+ }
822
+
823
+ const handler = {
824
+ get: (target, key, receiver) => {
825
+ if (key === '__raw__') return target;
826
+ if (key === REACTIVE_SYMBOLS.parent || key === '__parent__') return target[REACTIVE_SYMBOLS.parent];
827
+ if (key === REACTIVE_SYMBOLS.path || key === '__path__') return target[REACTIVE_SYMBOLS.path];
828
+ if (key === REACTIVE_SYMBOLS.isReactive || key === '__isReactive__') return true;
829
+
830
+ const result = Reflect.get(target, key, receiver);
831
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
832
+ return this.wrapReactive(result, `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? '.' : ''}${key}`);
833
+ }
834
+ return result;
835
+ },
836
+ set: (target, key, value, receiver) => {
837
+ if (key === REACTIVE_SYMBOLS.parent ||
838
+ key === REACTIVE_SYMBOLS.path ||
839
+ key === REACTIVE_SYMBOLS.isReactive ||
840
+ key === '__parent__' ||
841
+ key === '__path__' ||
842
+ key === '__isReactive__') {
843
+ return true;
844
+ }
845
+
846
+ const oldValue = Reflect.get(target, key, receiver);
847
+ const result = Reflect.set(target, key, value, receiver);
848
+
849
+ const fullPath = `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? '.' : ''}${key}`;
850
+ this.notify(fullPath, value, oldValue);
851
+ this.queueUpdate(fullPath, value);
852
+
853
+ return result;
854
+ },
855
+ deleteProperty: (target, key) => {
856
+ if (key === REACTIVE_SYMBOLS.parent ||
857
+ key === REACTIVE_SYMBOLS.path ||
858
+ key === REACTIVE_SYMBOLS.isReactive) {
859
+ return false;
860
+ }
861
+
862
+ const oldValue = Reflect.get(target, key);
863
+ const result = Reflect.deleteProperty(target, key);
864
+
865
+ const fullPath = `${target[REACTIVE_SYMBOLS.path]}${target[REACTIVE_SYMBOLS.path] ? '.' : ''}${key}`;
866
+ this.notify(fullPath, undefined, oldValue);
867
+ this.queueUpdate(fullPath, undefined);
868
+
869
+ return result;
870
+ },
871
+ has: (target, key) => {
872
+ if (key === '__raw__' ||
873
+ key === REACTIVE_SYMBOLS.parent ||
874
+ key === REACTIVE_SYMBOLS.path ||
875
+ key === REACTIVE_SYMBOLS.isReactive ||
876
+ key === '__parent__' ||
877
+ key === '__path__' ||
878
+ key === '__isReactive__') {
879
+ return true;
880
+ }
881
+ return key in target;
882
+ },
883
+ ownKeys: (target) => {
884
+ return Reflect.ownKeys(target).filter(key =>
885
+ key !== REACTIVE_SYMBOLS.parent &&
886
+ key !== REACTIVE_SYMBOLS.path &&
887
+ key !== REACTIVE_SYMBOLS.isReactive
888
+ );
889
+ },
890
+ getOwnPropertyDescriptor: (target, key) => {
891
+ if (key === REACTIVE_SYMBOLS.parent ||
892
+ key === REACTIVE_SYMBOLS.path ||
893
+ key === REACTIVE_SYMBOLS.isReactive) {
894
+ return {
895
+ configurable: false,
896
+ enumerable: false,
897
+ writable: false,
898
+ value: target[key]
899
+ };
900
+ }
901
+ return Reflect.getOwnPropertyDescriptor(target, key);
902
+ }
903
+ };
904
+
905
+ const proxy = new Proxy(target, handler);
906
+ target[REACTIVE_SYMBOLS.parent] = target;
907
+ target[REACTIVE_SYMBOLS.path] = path;
908
+ target[REACTIVE_SYMBOLS.isReactive] = true;
909
+ this._proxyCache.set(target, proxy);
910
+
911
+ Object.keys(target).forEach(key => {
912
+ if (target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
913
+ target[key] = this.wrapReactive(target[key], `${path}${path ? '.' : ''}${key}`);
914
+ }
915
+ });
916
+
917
+ return proxy;
918
+ }
919
+
920
+ bind() {
921
+ document.querySelectorAll('[data-bind]').forEach(element => {
922
+ this._bindElement(element);
923
+ });
924
+
925
+ if (!this._mutationObserver) {
926
+ this._mutationObserver = new MutationObserver((mutations) => {
927
+ mutations.forEach(mutation => {
928
+ mutation.addedNodes.forEach(node => {
929
+ if (node.nodeType === Node.ELEMENT_NODE) {
930
+ const bindElements = node.querySelectorAll('[data-bind]');
931
+ bindElements.forEach(element => this._bindElement(element));
932
+ if (node.hasAttribute && node.hasAttribute('data-bind')) {
933
+ this._bindElement(node);
934
+ }
935
+ }
936
+ });
937
+ });
938
+ });
939
+
940
+ this._mutationObserver.observe(document.body, {
941
+ childList: true,
942
+ subtree: true
943
+ });
944
+ }
945
+ }
946
+
947
+ _bindElement(element) {
948
+ const binding = element.getAttribute('data-bind');
949
+ const parts = binding.split(':');
950
+ const typePart = parts[0].split('|')[0].trim();
951
+ const key = parts[1]?.trim();
952
+
953
+ if (!key) return;
954
+
955
+ this.pathTrie.insert(key);
956
+
957
+ if (!this.elements[key]) {
958
+ this.elements[key] = [];
959
+ }
960
+
961
+ if (!this.elements[key].includes(element)) {
962
+ this.elements[key].push(element);
963
+ }
964
+
965
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') {
966
+ const existingHandler = element.__kupolaBindHandler;
967
+ if (existingHandler) {
968
+ element.removeEventListener('input', existingHandler);
969
+ }
970
+
971
+ const handler = () => {
972
+ const value = element.type === 'checkbox' ? element.checked : element.value;
973
+ if (key.includes('.')) {
974
+ this.setNested(key, value);
975
+ } else {
976
+ this.set(key, value);
977
+ }
978
+ };
979
+
980
+ element.__kupolaBindHandler = handler;
981
+ element.addEventListener('input', handler);
982
+ }
983
+
984
+ if (this.rawData[key] !== undefined) {
985
+ this.updateElement(element, this.rawData[key]);
986
+ }
987
+ }
988
+
989
+ destroy() {
990
+ if (this._mutationObserver) {
991
+ this._mutationObserver.disconnect();
992
+ this._mutationObserver = null;
993
+ }
994
+
995
+ Object.values(this.elements).forEach(elements => {
996
+ elements.forEach(element => {
997
+ const handler = element.__kupolaBindHandler;
998
+ if (handler) {
999
+ element.removeEventListener('input', handler);
1000
+ delete element.__kupolaBindHandler;
1001
+ }
1002
+ });
1003
+ });
1004
+
1005
+ this.persistedKeys.forEach((config, key) => {
1006
+ if (config.timeout) clearTimeout(config.timeout);
1007
+ });
1008
+ this.persistedKeys.clear();
1009
+
1010
+ this.rawData = {};
1011
+ this.observers = {};
1012
+ this.elements = {};
1013
+ this.computedProperties = {};
1014
+ this.pathTrie = new PathTrie();
1015
+ this.updateQueue.clear();
1016
+ this.snapshots = [];
1017
+ }
1018
+ }
1019
+
1020
+ class KupolaStore {
1021
+ constructor(name, options = {}) {
1022
+ this.name = name;
1023
+ this._stateKey = `__store_${name}__`;
1024
+
1025
+ const initialState = options.state ? options.state() : {};
1026
+ this.getters = options.getters || {};
1027
+ this.actions = options.actions || {};
1028
+ this.mutations = options.mutations || {};
1029
+
1030
+ this.observers = {};
1031
+
1032
+ if (window.kupolaData) {
1033
+ window.kupolaData.set(this._stateKey, initialState);
1034
+ this.state = window.kupolaData.data?.[this._stateKey] ||
1035
+ window.kupolaData.createReactive(initialState, this._stateKey);
1036
+
1037
+ window.kupolaData.observe(this._stateKey, (newState) => {
1038
+ this.notify(newState);
1039
+ });
1040
+ } else {
1041
+ this.state = initialState;
1042
+ }
1043
+
1044
+ this._bindGetters();
1045
+ this._bindActions();
1046
+ }
1047
+
1048
+ _bindGetters() {
1049
+ Object.keys(this.getters).forEach(name => {
1050
+ Object.defineProperty(this, name, {
1051
+ get: () => this.getters[name](this.state),
1052
+ enumerable: true
1053
+ });
1054
+ });
1055
+ }
1056
+
1057
+ _bindActions() {
1058
+ Object.keys(this.actions).forEach(name => {
1059
+ this[name] = (...args) => {
1060
+ return this.actions[name]({
1061
+ state: this.state,
1062
+ commit: this.commit.bind(this),
1063
+ dispatch: this.dispatch.bind(this),
1064
+ getters: this
1065
+ }, ...args);
1066
+ };
1067
+ });
1068
+ }
1069
+
1070
+ commit(type, payload) {
1071
+ const mutation = this.mutations[type];
1072
+ if (!mutation) {
1073
+ console.warn(`Mutation ${type} not found in store ${this.name}`);
1074
+ return;
1075
+ }
1076
+
1077
+ mutation(this.state, payload);
1078
+ }
1079
+
1080
+ dispatch(type, payload) {
1081
+ const action = this.actions[type];
1082
+ if (!action) {
1083
+ console.warn(`Action ${type} not found in store ${this.name}`);
1084
+ return;
1085
+ }
1086
+
1087
+ return action({
1088
+ state: this.state,
1089
+ commit: this.commit.bind(this),
1090
+ dispatch: this.dispatch.bind(this),
1091
+ getters: this
1092
+ }, payload);
1093
+ }
1094
+
1095
+ observe(callback) {
1096
+ if (!this.observers['*']) {
1097
+ this.observers['*'] = [];
1098
+ }
1099
+ this.observers['*'].push(callback);
1100
+ return callback;
1101
+ }
1102
+
1103
+ unobserve(callback) {
1104
+ if (this.observers['*']) {
1105
+ this.observers['*'] = this.observers['*'].filter(cb => cb !== callback);
1106
+ }
1107
+ }
1108
+
1109
+ notify(newState) {
1110
+ if (this.observers['*']) {
1111
+ this.observers['*'].forEach(callback => {
1112
+ try {
1113
+ callback(newState);
1114
+ } catch (e) {
1115
+ console.error(`Observer error for store ${this.name}:`, e);
1116
+ }
1117
+ });
1118
+ }
1119
+
1120
+ if (window.kupolaData) {
1121
+ window.kupolaData.set(this.name, newState);
1122
+ }
1123
+ }
1124
+
1125
+ toJSON() {
1126
+ return {
1127
+ name: this.name,
1128
+ state: this.state,
1129
+ getters: Object.keys(this.getters).reduce((acc, name) => {
1130
+ acc[name] = this[name];
1131
+ return acc;
1132
+ }, {})
1133
+ };
1134
+ }
1135
+ }
1136
+
1137
+ class KupolaStoreManager {
1138
+ constructor() {
1139
+ this.stores = new Map();
1140
+ }
1141
+
1142
+ createStore(name, options) {
1143
+ const store = new KupolaStore(name, options);
1144
+ this.stores.set(name, store);
1145
+ return store;
1146
+ }
1147
+
1148
+ getStore(name) {
1149
+ return this.stores.get(name);
1150
+ }
1151
+
1152
+ registerStore(store) {
1153
+ if (store instanceof KupolaStore) {
1154
+ this.stores.set(store.name, store);
1155
+ }
1156
+ }
1157
+
1158
+ dispose() {
1159
+ this.stores.clear();
1160
+ }
1161
+ }
1162
+
1163
+ class KupolaEventBus {
1164
+ constructor() {
1165
+ this.events = {};
1166
+ this.delegatedEvents = {};
1167
+ this.eventListeners = {};
1168
+ }
1169
+
1170
+ on(eventName, callback) {
1171
+ if (!this.events[eventName]) {
1172
+ this.events[eventName] = [];
1173
+ }
1174
+ this.events[eventName].push(callback);
1175
+ return callback;
1176
+ }
1177
+
1178
+ off(eventName, callback) {
1179
+ if (this.events[eventName]) {
1180
+ this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
1181
+ }
1182
+ }
1183
+
1184
+ emit(eventName, data) {
1185
+ if (this.events[eventName]) {
1186
+ this.events[eventName].forEach(callback => {
1187
+ try {
1188
+ callback(data);
1189
+ } catch (e) {
1190
+ console.error(`Error in event handler for ${eventName}:`, e);
1191
+ }
1192
+ });
1193
+ }
1194
+
1195
+ this.events['*']?.forEach(callback => {
1196
+ try {
1197
+ callback(eventName, data);
1198
+ } catch (e) {
1199
+ console.error(`Error in wildcard event handler:`, e);
1200
+ }
1201
+ });
1202
+ }
1203
+
1204
+ once(eventName, callback) {
1205
+ const onceCallback = (data) => {
1206
+ callback(data);
1207
+ this.off(eventName, onceCallback);
1208
+ };
1209
+ this.on(eventName, onceCallback);
1210
+ return onceCallback;
1211
+ }
1212
+
1213
+ delegate(selector, eventName, callback) {
1214
+ if (!this.delegatedEvents[eventName]) {
1215
+ this.delegatedEvents[eventName] = [];
1216
+
1217
+ const listener = (e) => {
1218
+ this.delegatedEvents[eventName].forEach(({ selector: sel, cb }) => {
1219
+ if (e.target.matches(sel) || e.target.closest(sel)) {
1220
+ cb(e);
1221
+ }
1222
+ });
1223
+ };
1224
+
1225
+ document.addEventListener(eventName, listener);
1226
+ this.eventListeners[eventName] = listener;
1227
+ }
1228
+
1229
+ this.delegatedEvents[eventName].push({ selector, cb: callback });
1230
+ return callback;
1231
+ }
1232
+
1233
+ undelegate(selector, eventName) {
1234
+ if (this.delegatedEvents[eventName]) {
1235
+ this.delegatedEvents[eventName] = this.delegatedEvents[eventName].filter(
1236
+ item => item.selector !== selector
1237
+ );
1238
+
1239
+ if (this.delegatedEvents[eventName].length === 0) {
1240
+ const listener = this.eventListeners[eventName];
1241
+ if (listener) {
1242
+ document.removeEventListener(eventName, listener);
1243
+ delete this.eventListeners[eventName];
1244
+ }
1245
+ delete this.delegatedEvents[eventName];
1246
+ }
1247
+ }
1248
+ }
1249
+
1250
+ destroy() {
1251
+ Object.entries(this.eventListeners).forEach(([eventName, listener]) => {
1252
+ document.removeEventListener(eventName, listener);
1253
+ });
1254
+
1255
+ this.events = {};
1256
+ this.delegatedEvents = {};
1257
+ this.eventListeners = {};
1258
+ }
1259
+ }
1260
+
1261
+ const kupolaData = new KupolaDataBind();
1262
+ const kupolaEvents = new KupolaEventBus();
1263
+ const kupolaStoreManager = new KupolaStoreManager();
1264
+
1265
+ function createStore(name, options) {
1266
+ return kupolaStoreManager.createStore(name, options);
1267
+ }
1268
+
1269
+ function getStore(name) {
1270
+ return kupolaStoreManager.getStore(name);
1271
+ }
1272
+
1273
+ document.addEventListener('DOMContentLoaded', () => {
1274
+ kupolaData.loadPersisted();
1275
+ kupolaData.bind();
1276
+ });
1277
+
1278
+ window.KupolaDataBind = KupolaDataBind;
1279
+ window.KupolaEventBus = KupolaEventBus;
1280
+ window.KupolaStore = KupolaStore;
1281
+ window.KupolaStoreManager = KupolaStoreManager;
1282
+ window.kupolaData = kupolaData;
1283
+ window.kupolaEvents = kupolaEvents;
1284
+ window.kupolaStoreManager = kupolaStoreManager;
1285
+ window.createStore = createStore;
1286
+ window.getStore = getStore;