create-microact-app 1.0.1

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 (213) hide show
  1. package/index.js +95 -0
  2. package/package.json +21 -0
  3. package/templates/vanilla/.github/workflows/deploy.yml +38 -0
  4. package/templates/vanilla/index.html +13 -0
  5. package/templates/vanilla/node_modules/.package-lock.json +207 -0
  6. package/templates/vanilla/node_modules/@esbuild/darwin-x64/README.md +3 -0
  7. package/templates/vanilla/node_modules/@esbuild/darwin-x64/bin/esbuild +0 -0
  8. package/templates/vanilla/node_modules/@esbuild/darwin-x64/package.json +17 -0
  9. package/templates/vanilla/node_modules/@monygroupcorp/microact/README.md +154 -0
  10. package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.cjs.js +1749 -0
  11. package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.cjs.js.map +1 -0
  12. package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.esm.js +1743 -0
  13. package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.esm.js.map +1 -0
  14. package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.umd.js +2 -0
  15. package/templates/vanilla/node_modules/@monygroupcorp/microact/dist/microact.umd.js.map +1 -0
  16. package/templates/vanilla/node_modules/@monygroupcorp/microact/example/index.html +13 -0
  17. package/templates/vanilla/node_modules/@monygroupcorp/microact/example/index.js +63 -0
  18. package/templates/vanilla/node_modules/@monygroupcorp/microact/package.json +38 -0
  19. package/templates/vanilla/node_modules/@monygroupcorp/microact/rollup.config.cjs +30 -0
  20. package/templates/vanilla/node_modules/@monygroupcorp/microact/src/Component.js +831 -0
  21. package/templates/vanilla/node_modules/@monygroupcorp/microact/src/DOMUpdater.js +320 -0
  22. package/templates/vanilla/node_modules/@monygroupcorp/microact/src/EventBus.js +123 -0
  23. package/templates/vanilla/node_modules/@monygroupcorp/microact/src/Router.js +253 -0
  24. package/templates/vanilla/node_modules/@monygroupcorp/microact/src/UpdateScheduler.js +218 -0
  25. package/templates/vanilla/node_modules/@monygroupcorp/microact/src/index.js +6 -0
  26. package/templates/vanilla/node_modules/esbuild/LICENSE.md +21 -0
  27. package/templates/vanilla/node_modules/esbuild/README.md +3 -0
  28. package/templates/vanilla/node_modules/esbuild/bin/esbuild +0 -0
  29. package/templates/vanilla/node_modules/esbuild/install.js +287 -0
  30. package/templates/vanilla/node_modules/esbuild/lib/main.d.ts +660 -0
  31. package/templates/vanilla/node_modules/esbuild/lib/main.js +2393 -0
  32. package/templates/vanilla/node_modules/esbuild/package.json +42 -0
  33. package/templates/vanilla/node_modules/nanoid/LICENSE +20 -0
  34. package/templates/vanilla/node_modules/nanoid/README.md +39 -0
  35. package/templates/vanilla/node_modules/nanoid/async/index.browser.cjs +69 -0
  36. package/templates/vanilla/node_modules/nanoid/async/index.browser.js +34 -0
  37. package/templates/vanilla/node_modules/nanoid/async/index.cjs +71 -0
  38. package/templates/vanilla/node_modules/nanoid/async/index.d.ts +56 -0
  39. package/templates/vanilla/node_modules/nanoid/async/index.js +35 -0
  40. package/templates/vanilla/node_modules/nanoid/async/index.native.js +26 -0
  41. package/templates/vanilla/node_modules/nanoid/async/package.json +12 -0
  42. package/templates/vanilla/node_modules/nanoid/bin/nanoid.cjs +55 -0
  43. package/templates/vanilla/node_modules/nanoid/index.browser.cjs +72 -0
  44. package/templates/vanilla/node_modules/nanoid/index.browser.js +34 -0
  45. package/templates/vanilla/node_modules/nanoid/index.cjs +85 -0
  46. package/templates/vanilla/node_modules/nanoid/index.d.cts +91 -0
  47. package/templates/vanilla/node_modules/nanoid/index.d.ts +91 -0
  48. package/templates/vanilla/node_modules/nanoid/index.js +45 -0
  49. package/templates/vanilla/node_modules/nanoid/nanoid.js +1 -0
  50. package/templates/vanilla/node_modules/nanoid/non-secure/index.cjs +34 -0
  51. package/templates/vanilla/node_modules/nanoid/non-secure/index.d.ts +33 -0
  52. package/templates/vanilla/node_modules/nanoid/non-secure/index.js +21 -0
  53. package/templates/vanilla/node_modules/nanoid/non-secure/package.json +6 -0
  54. package/templates/vanilla/node_modules/nanoid/package.json +89 -0
  55. package/templates/vanilla/node_modules/nanoid/url-alphabet/index.cjs +7 -0
  56. package/templates/vanilla/node_modules/nanoid/url-alphabet/index.js +3 -0
  57. package/templates/vanilla/node_modules/nanoid/url-alphabet/package.json +6 -0
  58. package/templates/vanilla/node_modules/picocolors/LICENSE +15 -0
  59. package/templates/vanilla/node_modules/picocolors/README.md +21 -0
  60. package/templates/vanilla/node_modules/picocolors/package.json +25 -0
  61. package/templates/vanilla/node_modules/picocolors/picocolors.browser.js +4 -0
  62. package/templates/vanilla/node_modules/picocolors/picocolors.d.ts +5 -0
  63. package/templates/vanilla/node_modules/picocolors/picocolors.js +75 -0
  64. package/templates/vanilla/node_modules/picocolors/types.d.ts +51 -0
  65. package/templates/vanilla/node_modules/postcss/LICENSE +20 -0
  66. package/templates/vanilla/node_modules/postcss/README.md +29 -0
  67. package/templates/vanilla/node_modules/postcss/lib/at-rule.d.ts +140 -0
  68. package/templates/vanilla/node_modules/postcss/lib/at-rule.js +25 -0
  69. package/templates/vanilla/node_modules/postcss/lib/comment.d.ts +68 -0
  70. package/templates/vanilla/node_modules/postcss/lib/comment.js +13 -0
  71. package/templates/vanilla/node_modules/postcss/lib/container.d.ts +483 -0
  72. package/templates/vanilla/node_modules/postcss/lib/container.js +447 -0
  73. package/templates/vanilla/node_modules/postcss/lib/css-syntax-error.d.ts +248 -0
  74. package/templates/vanilla/node_modules/postcss/lib/css-syntax-error.js +133 -0
  75. package/templates/vanilla/node_modules/postcss/lib/declaration.d.ts +151 -0
  76. package/templates/vanilla/node_modules/postcss/lib/declaration.js +24 -0
  77. package/templates/vanilla/node_modules/postcss/lib/document.d.ts +69 -0
  78. package/templates/vanilla/node_modules/postcss/lib/document.js +33 -0
  79. package/templates/vanilla/node_modules/postcss/lib/fromJSON.d.ts +9 -0
  80. package/templates/vanilla/node_modules/postcss/lib/fromJSON.js +54 -0
  81. package/templates/vanilla/node_modules/postcss/lib/input.d.ts +227 -0
  82. package/templates/vanilla/node_modules/postcss/lib/input.js +265 -0
  83. package/templates/vanilla/node_modules/postcss/lib/lazy-result.d.ts +190 -0
  84. package/templates/vanilla/node_modules/postcss/lib/lazy-result.js +550 -0
  85. package/templates/vanilla/node_modules/postcss/lib/list.d.ts +60 -0
  86. package/templates/vanilla/node_modules/postcss/lib/list.js +58 -0
  87. package/templates/vanilla/node_modules/postcss/lib/map-generator.js +368 -0
  88. package/templates/vanilla/node_modules/postcss/lib/no-work-result.d.ts +46 -0
  89. package/templates/vanilla/node_modules/postcss/lib/no-work-result.js +138 -0
  90. package/templates/vanilla/node_modules/postcss/lib/node.d.ts +556 -0
  91. package/templates/vanilla/node_modules/postcss/lib/node.js +449 -0
  92. package/templates/vanilla/node_modules/postcss/lib/parse.d.ts +9 -0
  93. package/templates/vanilla/node_modules/postcss/lib/parse.js +42 -0
  94. package/templates/vanilla/node_modules/postcss/lib/parser.js +611 -0
  95. package/templates/vanilla/node_modules/postcss/lib/postcss.d.mts +69 -0
  96. package/templates/vanilla/node_modules/postcss/lib/postcss.d.ts +458 -0
  97. package/templates/vanilla/node_modules/postcss/lib/postcss.js +101 -0
  98. package/templates/vanilla/node_modules/postcss/lib/postcss.mjs +30 -0
  99. package/templates/vanilla/node_modules/postcss/lib/previous-map.d.ts +81 -0
  100. package/templates/vanilla/node_modules/postcss/lib/previous-map.js +144 -0
  101. package/templates/vanilla/node_modules/postcss/lib/processor.d.ts +115 -0
  102. package/templates/vanilla/node_modules/postcss/lib/processor.js +67 -0
  103. package/templates/vanilla/node_modules/postcss/lib/result.d.ts +205 -0
  104. package/templates/vanilla/node_modules/postcss/lib/result.js +42 -0
  105. package/templates/vanilla/node_modules/postcss/lib/root.d.ts +87 -0
  106. package/templates/vanilla/node_modules/postcss/lib/root.js +61 -0
  107. package/templates/vanilla/node_modules/postcss/lib/rule.d.ts +126 -0
  108. package/templates/vanilla/node_modules/postcss/lib/rule.js +27 -0
  109. package/templates/vanilla/node_modules/postcss/lib/stringifier.d.ts +46 -0
  110. package/templates/vanilla/node_modules/postcss/lib/stringifier.js +353 -0
  111. package/templates/vanilla/node_modules/postcss/lib/stringify.d.ts +9 -0
  112. package/templates/vanilla/node_modules/postcss/lib/stringify.js +11 -0
  113. package/templates/vanilla/node_modules/postcss/lib/symbols.js +5 -0
  114. package/templates/vanilla/node_modules/postcss/lib/terminal-highlight.js +70 -0
  115. package/templates/vanilla/node_modules/postcss/lib/tokenize.js +266 -0
  116. package/templates/vanilla/node_modules/postcss/lib/warn-once.js +13 -0
  117. package/templates/vanilla/node_modules/postcss/lib/warning.d.ts +147 -0
  118. package/templates/vanilla/node_modules/postcss/lib/warning.js +37 -0
  119. package/templates/vanilla/node_modules/postcss/package.json +88 -0
  120. package/templates/vanilla/node_modules/rollup/LICENSE.md +695 -0
  121. package/templates/vanilla/node_modules/rollup/README.md +125 -0
  122. package/templates/vanilla/node_modules/rollup/dist/bin/rollup +1715 -0
  123. package/templates/vanilla/node_modules/rollup/dist/es/getLogFilter.js +64 -0
  124. package/templates/vanilla/node_modules/rollup/dist/es/package.json +1 -0
  125. package/templates/vanilla/node_modules/rollup/dist/es/rollup.js +17 -0
  126. package/templates/vanilla/node_modules/rollup/dist/es/shared/node-entry.js +27273 -0
  127. package/templates/vanilla/node_modules/rollup/dist/es/shared/watch.js +4857 -0
  128. package/templates/vanilla/node_modules/rollup/dist/getLogFilter.d.ts +5 -0
  129. package/templates/vanilla/node_modules/rollup/dist/getLogFilter.js +69 -0
  130. package/templates/vanilla/node_modules/rollup/dist/loadConfigFile.d.ts +20 -0
  131. package/templates/vanilla/node_modules/rollup/dist/loadConfigFile.js +29 -0
  132. package/templates/vanilla/node_modules/rollup/dist/rollup.d.ts +1012 -0
  133. package/templates/vanilla/node_modules/rollup/dist/rollup.js +31 -0
  134. package/templates/vanilla/node_modules/rollup/dist/shared/fsevents-importer.js +37 -0
  135. package/templates/vanilla/node_modules/rollup/dist/shared/index.js +4571 -0
  136. package/templates/vanilla/node_modules/rollup/dist/shared/loadConfigFile.js +546 -0
  137. package/templates/vanilla/node_modules/rollup/dist/shared/rollup.js +27351 -0
  138. package/templates/vanilla/node_modules/rollup/dist/shared/watch-cli.js +561 -0
  139. package/templates/vanilla/node_modules/rollup/dist/shared/watch-proxy.js +87 -0
  140. package/templates/vanilla/node_modules/rollup/dist/shared/watch.js +316 -0
  141. package/templates/vanilla/node_modules/rollup/package.json +181 -0
  142. package/templates/vanilla/node_modules/source-map-js/LICENSE +28 -0
  143. package/templates/vanilla/node_modules/source-map-js/README.md +765 -0
  144. package/templates/vanilla/node_modules/source-map-js/lib/array-set.js +121 -0
  145. package/templates/vanilla/node_modules/source-map-js/lib/base64-vlq.js +140 -0
  146. package/templates/vanilla/node_modules/source-map-js/lib/base64.js +67 -0
  147. package/templates/vanilla/node_modules/source-map-js/lib/binary-search.js +111 -0
  148. package/templates/vanilla/node_modules/source-map-js/lib/mapping-list.js +79 -0
  149. package/templates/vanilla/node_modules/source-map-js/lib/quick-sort.js +132 -0
  150. package/templates/vanilla/node_modules/source-map-js/lib/source-map-consumer.d.ts +1 -0
  151. package/templates/vanilla/node_modules/source-map-js/lib/source-map-consumer.js +1188 -0
  152. package/templates/vanilla/node_modules/source-map-js/lib/source-map-generator.d.ts +1 -0
  153. package/templates/vanilla/node_modules/source-map-js/lib/source-map-generator.js +444 -0
  154. package/templates/vanilla/node_modules/source-map-js/lib/source-node.d.ts +1 -0
  155. package/templates/vanilla/node_modules/source-map-js/lib/source-node.js +413 -0
  156. package/templates/vanilla/node_modules/source-map-js/lib/util.js +594 -0
  157. package/templates/vanilla/node_modules/source-map-js/package.json +71 -0
  158. package/templates/vanilla/node_modules/source-map-js/source-map.d.ts +104 -0
  159. package/templates/vanilla/node_modules/source-map-js/source-map.js +8 -0
  160. package/templates/vanilla/node_modules/vite/LICENSE.md +3396 -0
  161. package/templates/vanilla/node_modules/vite/README.md +20 -0
  162. package/templates/vanilla/node_modules/vite/bin/openChrome.applescript +95 -0
  163. package/templates/vanilla/node_modules/vite/bin/vite.js +61 -0
  164. package/templates/vanilla/node_modules/vite/client.d.ts +281 -0
  165. package/templates/vanilla/node_modules/vite/dist/client/client.mjs +725 -0
  166. package/templates/vanilla/node_modules/vite/dist/client/client.mjs.map +1 -0
  167. package/templates/vanilla/node_modules/vite/dist/client/env.mjs +30 -0
  168. package/templates/vanilla/node_modules/vite/dist/client/env.mjs.map +1 -0
  169. package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-7ec6f216.js +914 -0
  170. package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-827b23df.js +66713 -0
  171. package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-c423598f.js +561 -0
  172. package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-f0c7dae0.js +7930 -0
  173. package/templates/vanilla/node_modules/vite/dist/node/chunks/dep-f1e8587f.js +7646 -0
  174. package/templates/vanilla/node_modules/vite/dist/node/cli.js +929 -0
  175. package/templates/vanilla/node_modules/vite/dist/node/constants.js +130 -0
  176. package/templates/vanilla/node_modules/vite/dist/node/index.d.ts +3548 -0
  177. package/templates/vanilla/node_modules/vite/dist/node/index.js +158 -0
  178. package/templates/vanilla/node_modules/vite/dist/node-cjs/publicUtils.cjs +4555 -0
  179. package/templates/vanilla/node_modules/vite/index.cjs +34 -0
  180. package/templates/vanilla/node_modules/vite/package.json +173 -0
  181. package/templates/vanilla/node_modules/vite/types/customEvent.d.ts +35 -0
  182. package/templates/vanilla/node_modules/vite/types/hmrPayload.d.ts +61 -0
  183. package/templates/vanilla/node_modules/vite/types/hot.d.ts +32 -0
  184. package/templates/vanilla/node_modules/vite/types/importGlob.d.ts +97 -0
  185. package/templates/vanilla/node_modules/vite/types/importMeta.d.ts +26 -0
  186. package/templates/vanilla/node_modules/vite/types/metadata.d.ts +10 -0
  187. package/templates/vanilla/node_modules/vite/types/package.json +4 -0
  188. package/templates/vanilla/package-lock.json +589 -0
  189. package/templates/vanilla/package.json +17 -0
  190. package/templates/vanilla/src/components/App.js +60 -0
  191. package/templates/vanilla/src/components/Card.js +21 -0
  192. package/templates/vanilla/src/components/Hero.js +15 -0
  193. package/templates/vanilla/src/components/InteractiveDemo.js +59 -0
  194. package/templates/vanilla/src/main.js +9 -0
  195. package/templates/vanilla/src/style/main.css +172 -0
  196. package/templates/vanilla/vite.config.js +8 -0
  197. package/templates/web3/.env.example +15 -0
  198. package/templates/web3/.github/workflows/deploy.yml +38 -0
  199. package/templates/web3/README.md +33 -0
  200. package/templates/web3/contracts/foundry.toml +11 -0
  201. package/templates/web3/contracts/script/Deploy.s.sol +13 -0
  202. package/templates/web3/contracts/src/Counter.sol +21 -0
  203. package/templates/web3/index.html +13 -0
  204. package/templates/web3/package.json +25 -0
  205. package/templates/web3/scripts/chain-start.mjs +305 -0
  206. package/templates/web3/scripts/chain-stop.mjs +34 -0
  207. package/templates/web3/scripts/deploy.mjs +155 -0
  208. package/templates/web3/scripts/setup.mjs +42 -0
  209. package/templates/web3/src/components/App.js +49 -0
  210. package/templates/web3/src/components/CounterCard.js +111 -0
  211. package/templates/web3/src/main.js +54 -0
  212. package/templates/web3/src/style/main.css +345 -0
  213. package/templates/web3/vite.config.js +29 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * DOMUpdater - Utility for granular DOM updates
3
+ * Preserves focus state, scroll position, and form input values during updates
4
+ */
5
+ export class DOMUpdater {
6
+ constructor() {
7
+ this._activeElement = null;
8
+ this._activeElementPath = null;
9
+ this._scrollPositions = new Map();
10
+ }
11
+
12
+ /**
13
+ * Save the currently focused element and scroll positions
14
+ * @param {HTMLElement} rootElement - Root element to save state from
15
+ */
16
+ saveState(rootElement) {
17
+ // Save active element
18
+ const activeElement = document.activeElement;
19
+ if (activeElement && rootElement.contains(activeElement)) {
20
+ this._activeElement = activeElement;
21
+ this._activeElementPath = this._getElementPath(activeElement, rootElement);
22
+ } else {
23
+ this._activeElement = null;
24
+ this._activeElementPath = null;
25
+ }
26
+
27
+ // Save scroll positions for all scrollable containers
28
+ this._scrollPositions.clear();
29
+ const scrollableElements = rootElement.querySelectorAll('[data-scroll-container], [style*="overflow"]');
30
+ scrollableElements.forEach(el => {
31
+ if (el.scrollTop !== undefined || el.scrollLeft !== undefined) {
32
+ const path = this._getElementPath(el, rootElement);
33
+ this._scrollPositions.set(path, {
34
+ scrollTop: el.scrollTop,
35
+ scrollLeft: el.scrollLeft
36
+ });
37
+ }
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Restore focus and scroll positions after DOM update
43
+ * @param {HTMLElement} rootElement - Root element to restore state in
44
+ */
45
+ restoreState(rootElement) {
46
+ // Restore focus
47
+ if (this._activeElementPath) {
48
+ const element = this._getElementByPath(rootElement, this._activeElementPath);
49
+ if (element && element.focus) {
50
+ try {
51
+ // Restore cursor position if it's an input/textarea
52
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
53
+ const selectionStart = element.selectionStart || 0;
54
+ element.focus();
55
+ if (element.setSelectionRange) {
56
+ element.setSelectionRange(selectionStart, selectionStart);
57
+ }
58
+ } else {
59
+ element.focus();
60
+ }
61
+ } catch (e) {
62
+ // Focus may fail if element is not focusable, ignore
63
+ }
64
+ }
65
+ }
66
+
67
+ // Restore scroll positions
68
+ this._scrollPositions.forEach((position, path) => {
69
+ const element = this._getElementByPath(rootElement, path);
70
+ if (element) {
71
+ element.scrollTop = position.scrollTop;
72
+ element.scrollLeft = position.scrollLeft;
73
+ }
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Get a path string to identify an element within its root
79
+ * @private
80
+ */
81
+ _getElementPath(element, root) {
82
+ const path = [];
83
+ let current = element;
84
+
85
+ while (current && current !== root && current.parentNode) {
86
+ const parent = current.parentNode;
87
+ const index = Array.from(parent.children).indexOf(current);
88
+ path.unshift(index);
89
+ current = parent;
90
+ }
91
+
92
+ return path.join('/');
93
+ }
94
+
95
+ /**
96
+ * Get an element by its path within root
97
+ * @private
98
+ */
99
+ _getElementByPath(root, path) {
100
+ const indices = path.split('/').map(Number);
101
+ let current = root;
102
+
103
+ for (const index of indices) {
104
+ if (!current.children || !current.children[index]) {
105
+ return null;
106
+ }
107
+ current = current.children[index];
108
+ }
109
+
110
+ return current;
111
+ }
112
+
113
+ /**
114
+ * Compare two HTML strings and determine if they're structurally different
115
+ * @param {string} oldHTML - Previous HTML
116
+ * @param {string} newHTML - New HTML
117
+ * @returns {boolean} - True if structure is significantly different
118
+ */
119
+ diffHTML(oldHTML, newHTML) {
120
+ if (oldHTML === newHTML) return false;
121
+
122
+ // Simple heuristic: if length difference is > 50%, likely structural change
123
+ const lengthDiff = Math.abs(oldHTML.length - newHTML.length);
124
+ const avgLength = (oldHTML.length + newHTML.length) / 2;
125
+ if (lengthDiff / avgLength > 0.5) {
126
+ return true;
127
+ }
128
+
129
+ // Check for tag changes (simplified)
130
+ const oldTags = oldHTML.match(/<[^>]+>/g) || [];
131
+ const newTags = newHTML.match(/<[^>]+>/g) || [];
132
+ if (oldTags.length !== newTags.length) {
133
+ return true;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Update text content of a node
141
+ * @param {Node} node - Node to update
142
+ * @param {string} newText - New text content
143
+ */
144
+ updateText(node, newText) {
145
+ if (node.nodeType === Node.TEXT_NODE) {
146
+ node.textContent = newText;
147
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
148
+ // For elements, update only if it's a text-only element
149
+ if (node.children.length === 0) {
150
+ node.textContent = newText;
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Update attributes of an element
157
+ * @param {HTMLElement} element - Element to update
158
+ * @param {Object} newAttrs - New attributes object
159
+ */
160
+ updateAttributes(element, newAttrs) {
161
+ if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
162
+
163
+ // Get current attributes
164
+ const currentAttrs = {};
165
+ Array.from(element.attributes).forEach(attr => {
166
+ currentAttrs[attr.name] = attr.value;
167
+ });
168
+
169
+ // Update changed attributes
170
+ Object.keys(newAttrs).forEach(name => {
171
+ const newValue = newAttrs[name];
172
+ if (currentAttrs[name] !== newValue) {
173
+ if (newValue === null || newValue === undefined) {
174
+ element.removeAttribute(name);
175
+ } else {
176
+ element.setAttribute(name, newValue);
177
+ }
178
+ }
179
+ });
180
+
181
+ // Remove attributes that are no longer present
182
+ Object.keys(currentAttrs).forEach(name => {
183
+ if (!(name in newAttrs)) {
184
+ element.removeAttribute(name);
185
+ }
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Update children of a parent element using a simple reconciliation algorithm
191
+ * @param {HTMLElement} parent - Parent element
192
+ * @param {NodeList|Array} oldChildren - Current children
193
+ * @param {DocumentFragment|HTMLElement} newChildrenContainer - Container with new children
194
+ */
195
+ updateChildren(parent, oldChildren, newChildrenContainer) {
196
+ const oldArray = Array.from(oldChildren);
197
+ const newArray = Array.from(newChildrenContainer.children || []);
198
+
199
+ // If structure is too different, replace entirely
200
+ if (Math.abs(oldArray.length - newArray.length) > oldArray.length * 0.3) {
201
+ return false; // Signal to fall back to full replacement
202
+ }
203
+
204
+ // Simple reconciliation: update in place where possible
205
+ const maxLength = Math.max(oldArray.length, newArray.length);
206
+
207
+ for (let i = 0; i < maxLength; i++) {
208
+ const oldChild = oldArray[i];
209
+ const newChild = newArray[i];
210
+
211
+ if (!oldChild && newChild) {
212
+ // New child - append
213
+ parent.appendChild(newChild);
214
+ } else if (oldChild && !newChild) {
215
+ // Old child removed
216
+ oldChild.remove();
217
+ } else if (oldChild && newChild) {
218
+ // Both exist - try to update in place
219
+ if (this._canUpdateInPlace(oldChild, newChild)) {
220
+ this._updateNodeInPlace(oldChild, newChild);
221
+ } else {
222
+ // Replace
223
+ parent.replaceChild(newChild, oldChild);
224
+ }
225
+ }
226
+ }
227
+
228
+ return true; // Successfully updated
229
+ }
230
+
231
+ /**
232
+ * Check if two nodes can be updated in place
233
+ * @private
234
+ */
235
+ _canUpdateInPlace(oldNode, newNode) {
236
+ if (oldNode.nodeType !== newNode.nodeType) return false;
237
+ if (oldNode.nodeType === Node.TEXT_NODE) return true;
238
+ if (oldNode.nodeType === Node.ELEMENT_NODE) {
239
+ return oldNode.tagName === newNode.tagName;
240
+ }
241
+ return false;
242
+ }
243
+
244
+ /**
245
+ * Update a node in place with new content
246
+ * @private
247
+ */
248
+ _updateNodeInPlace(oldNode, newNode) {
249
+ if (oldNode.nodeType === Node.TEXT_NODE) {
250
+ oldNode.textContent = newNode.textContent;
251
+ return;
252
+ }
253
+
254
+ if (oldNode.nodeType === Node.ELEMENT_NODE) {
255
+ // Update attributes
256
+ const newAttrs = {};
257
+ Array.from(newNode.attributes).forEach(attr => {
258
+ newAttrs[attr.name] = attr.value;
259
+ });
260
+ this.updateAttributes(oldNode, newAttrs);
261
+
262
+ // Update children recursively
263
+ const oldChildren = oldNode.childNodes;
264
+ const newChildren = newNode.childNodes;
265
+
266
+ // For simple text-only updates
267
+ if (oldChildren.length === 1 && newChildren.length === 1 &&
268
+ oldChildren[0].nodeType === Node.TEXT_NODE &&
269
+ newChildren[0].nodeType === Node.TEXT_NODE) {
270
+ oldChildren[0].textContent = newChildren[0].textContent;
271
+ return;
272
+ }
273
+
274
+ // For more complex structures, try to reconcile
275
+ if (!this.updateChildren(oldNode, oldChildren, newNode)) {
276
+ // Fall back to replacing content
277
+ oldNode.innerHTML = newNode.innerHTML;
278
+ }
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Perform a granular update of an element's content
284
+ * @param {HTMLElement} element - Element to update
285
+ * @param {string} newHTML - New HTML content
286
+ * @returns {boolean} - True if granular update succeeded, false if fallback needed
287
+ */
288
+ updateGranular(element, newHTML) {
289
+ // Save state before update
290
+ this.saveState(element);
291
+
292
+ // Create temporary container for new HTML
293
+ const temp = document.createElement('div');
294
+ temp.innerHTML = newHTML;
295
+
296
+ // Try to update children granularly
297
+ const oldHTML = element.innerHTML;
298
+ const isStructuralChange = this.diffHTML(oldHTML, newHTML);
299
+
300
+ if (isStructuralChange) {
301
+ // Structural change detected - fall back to full replacement
302
+ return false;
303
+ }
304
+
305
+ // Try granular update
306
+ try {
307
+ const success = this.updateChildren(element, element.childNodes, temp);
308
+ if (success) {
309
+ // Restore state after successful update
310
+ this.restoreState(element);
311
+ return true;
312
+ }
313
+ } catch (e) {
314
+ console.warn('[DOMUpdater] Granular update failed, falling back:', e);
315
+ }
316
+
317
+ return false;
318
+ }
319
+ }
320
+
@@ -0,0 +1,123 @@
1
+ class EventBus {
2
+ constructor() {
3
+ this.listeners = new Map();
4
+ this.debugMode = false;
5
+ }
6
+
7
+ /**
8
+ * Enable or disable debug logging
9
+ * @param {boolean} enabled
10
+ */
11
+ setDebugMode(enabled) {
12
+ this.debugMode = enabled;
13
+ }
14
+
15
+ /**
16
+ * Subscribe to an event
17
+ * @param {string} eventName
18
+ * @param {Function} callback
19
+ * @returns {Function} Unsubscribe function
20
+ */
21
+ on(eventName, callback) {
22
+ if (!this.listeners.has(eventName)) {
23
+ this.listeners.set(eventName, new Set());
24
+ }
25
+
26
+ this.listeners.get(eventName).add(callback);
27
+
28
+ if (this.debugMode) {
29
+ console.log(`[EventBus] Listener added for "${eventName}"`);
30
+ }
31
+
32
+ // Return unsubscribe function
33
+ return () => this.off(eventName, callback);
34
+ }
35
+
36
+ /**
37
+ * Remove a specific event listener
38
+ * @param {string} eventName
39
+ * @param {Function} callback
40
+ */
41
+ off(eventName, callback) {
42
+ if (!this.listeners.has(eventName)) return;
43
+
44
+ this.listeners.get(eventName).delete(callback);
45
+
46
+ if (this.debugMode) {
47
+ console.log(`[EventBus] Listener removed for "${eventName}"`);
48
+ }
49
+
50
+ // Cleanup empty event sets
51
+ if (this.listeners.get(eventName).size === 0) {
52
+ this.listeners.delete(eventName);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Remove all listeners for an event
58
+ * @param {string} eventName
59
+ */
60
+ removeAllListeners(eventName) {
61
+ if (eventName) {
62
+ this.listeners.delete(eventName);
63
+ if (this.debugMode) {
64
+ console.log(`[EventBus] All listeners removed for "${eventName}"`);
65
+ }
66
+ } else {
67
+ this.listeners.clear();
68
+ if (this.debugMode) {
69
+ console.log('[EventBus] All listeners removed');
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Emit an event with data
76
+ * @param {string} eventName
77
+ * @param {any} data
78
+ */
79
+ emit(eventName, data) {
80
+ if (!this.listeners.has(eventName)) {
81
+ return;
82
+ }
83
+
84
+ if (this.debugMode) {
85
+ console.log(`[EventBus] Emitting "${eventName}"`, data);
86
+ }
87
+
88
+ this.listeners.get(eventName).forEach(callback => {
89
+ try {
90
+ callback(data);
91
+ } catch (error) {
92
+ console.error(`[EventBus] Error in listener for "${eventName}":`, error);
93
+ }
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Subscribe to an event for one-time use
99
+ * @param {string} eventName
100
+ * @param {Function} callback
101
+ * @returns {Function} Unsubscribe function
102
+ */
103
+ once(eventName, callback) {
104
+ // Create a wrapper that will call the callback and then unsubscribe
105
+ const wrappedCallback = (data) => {
106
+ // Unsubscribe first to prevent issues if the callback triggers the same event
107
+ this.off(eventName, wrappedCallback);
108
+ // Call the original callback
109
+ try {
110
+ callback(data);
111
+ } catch (error) {
112
+ console.error(`[EventBus] Error in once callback for "${eventName}":`, error);
113
+ }
114
+ };
115
+
116
+ // Register the wrapped callback
117
+ const unsubscribe = this.on(eventName, wrappedCallback);
118
+ return unsubscribe;
119
+ }
120
+ }
121
+
122
+ // Create a single instance for the application
123
+ export const eventBus = new EventBus();
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Simple client-side router for SPA navigation
3
+ * Supports static routes and browser history
4
+ */
5
+ class Router {
6
+ constructor() {
7
+ this.routes = new Map();
8
+ this.currentRoute = null;
9
+ this.currentHandler = null;
10
+ this.notFoundHandler = null;
11
+
12
+ // Bind methods
13
+ this.handleRoute = this.handleRoute.bind(this);
14
+ this.navigate = this.navigate.bind(this);
15
+
16
+ // Listen for browser back/forward
17
+ window.addEventListener('popstate', async (e) => {
18
+ await this.handleRoute(window.location.pathname);
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Register a route
24
+ * @param {string} path - Route path (e.g., '/', '/cultexecs')
25
+ * @param {Function} handler - Route handler function
26
+ */
27
+ on(path, handler) {
28
+ this.routes.set(path, handler);
29
+ }
30
+
31
+ /**
32
+ * Register a 404 handler
33
+ * @param {Function} handler - Handler for unmatched routes
34
+ */
35
+ notFound(handler) {
36
+ this.notFoundHandler = handler;
37
+ }
38
+
39
+ /**
40
+ * Navigate to a route
41
+ * @param {string} path - Route path
42
+ * @param {boolean} replace - Whether to replace history entry
43
+ */
44
+ async navigate(path, replace = false) {
45
+ if (path === window.location.pathname) {
46
+ return; // Already on this route
47
+ }
48
+
49
+ if (replace) {
50
+ window.history.replaceState({ path }, '', path);
51
+ } else {
52
+ window.history.pushState({ path }, '', path);
53
+ }
54
+
55
+ await this.handleRoute(path);
56
+
57
+
58
+ }
59
+
60
+ /**
61
+ * Match a path against a route pattern
62
+ * @param {string} pattern - Route pattern (e.g., '/project/:id')
63
+ * @param {string} path - Actual path to match
64
+ * @returns {object|null} Matched params or null if no match
65
+ * @private
66
+ */
67
+ _matchRoute(pattern, path) {
68
+ // Exact match for static routes
69
+ if (pattern === path) {
70
+ return {};
71
+ }
72
+
73
+ // Split into parts, filtering empty strings
74
+ const patternParts = pattern.split('/').filter(p => p);
75
+ const pathParts = path.split('/').filter(p => p);
76
+
77
+ // Must have same number of parts
78
+ if (patternParts.length !== pathParts.length) {
79
+ return null;
80
+ }
81
+
82
+ const params = {};
83
+ for (let i = 0; i < patternParts.length; i++) {
84
+ const patternPart = patternParts[i];
85
+ const pathPart = pathParts[i];
86
+
87
+ // Check if this is a parameter (starts with :)
88
+ if (patternPart.startsWith(':')) {
89
+ const paramName = patternPart.slice(1);
90
+ // Decode URL component
91
+ try {
92
+ params[paramName] = decodeURIComponent(pathPart);
93
+ } catch (e) {
94
+ // If decoding fails, use raw value
95
+ params[paramName] = pathPart;
96
+ }
97
+ } else if (patternPart !== pathPart) {
98
+ // Static part doesn't match
99
+ return null;
100
+ }
101
+ }
102
+
103
+ return params;
104
+ }
105
+
106
+ /**
107
+ * Find matching route handler
108
+ * @param {string} path - Route path
109
+ * @returns {object|null} { handler, params } or null
110
+ * @private
111
+ */
112
+ _findRoute(path) {
113
+ // First check for exact static route match (static routes take precedence)
114
+ if (this.routes.has(path)) {
115
+ return {
116
+ handler: this.routes.get(path),
117
+ params: {}
118
+ };
119
+ }
120
+
121
+ // Collect all dynamic routes and sort by specificity
122
+ const dynamicRoutes = [];
123
+ for (const [pattern, handler] of this.routes.entries()) {
124
+ // Skip if it's an exact match (already checked above)
125
+ if (pattern === path) {
126
+ continue;
127
+ }
128
+
129
+ // Check if pattern contains dynamic parameters
130
+ if (pattern.includes(':')) {
131
+ const paramCount = (pattern.match(/:/g) || []).length;
132
+ // Count literal (non-param) parts for better specificity
133
+ const literalCount = pattern.split('/').filter(p => p && !p.startsWith(':')).length;
134
+ dynamicRoutes.push({ pattern, handler, paramCount, literalCount });
135
+ }
136
+ }
137
+
138
+ // Sort by literal count first (more literals = more specific), then by param count
139
+ // This ensures routes with literal parts (like /create) are matched before fully dynamic routes
140
+ dynamicRoutes.sort((a, b) => {
141
+ if (b.literalCount !== a.literalCount) {
142
+ return b.literalCount - a.literalCount;
143
+ }
144
+ return b.paramCount - a.paramCount;
145
+ });
146
+
147
+ // Try each route in order of specificity
148
+ for (const { pattern, handler } of dynamicRoutes) {
149
+ const params = this._matchRoute(pattern, path);
150
+ if (params !== null) {
151
+ return { handler, params };
152
+ }
153
+ }
154
+
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * Handle route change
160
+ * @param {string} path - Route path
161
+ */
162
+ async handleRoute(path) {
163
+ // Clean up current handler if it exists
164
+ if (this.currentHandler && typeof this.currentHandler.cleanup === 'function') {
165
+ this.currentHandler.cleanup();
166
+ }
167
+
168
+ // Find matching route
169
+ const match = this._findRoute(path);
170
+
171
+ if (match) {
172
+ this.currentRoute = path;
173
+ // Call handler with params and store the result (which may include cleanup function)
174
+ // Handle both sync and async handlers
175
+ const result = await Promise.resolve(match.handler(match.params));
176
+ this.currentHandler = result || null;
177
+
178
+
179
+ } else if (this.notFoundHandler) {
180
+ this.currentRoute = null;
181
+ this.currentHandler = null;
182
+ this.notFoundHandler(path);
183
+ } else {
184
+ console.warn(`No route handler for: ${path}`);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Start the router
190
+ */
191
+ async start() {
192
+ // Handle initial route
193
+ await this.handleRoute(window.location.pathname);
194
+ }
195
+
196
+ /**
197
+ * Get current route
198
+ */
199
+ getCurrentRoute() {
200
+ return this.currentRoute || window.location.pathname;
201
+ }
202
+
203
+ /**
204
+ * Encode a title for use in URL (slug)
205
+ * @param {string} title - Title to encode
206
+ * @returns {string} URL-safe slug
207
+ */
208
+ _encodeTitle(title) {
209
+ if (!title) return '';
210
+ return title
211
+ .toLowerCase()
212
+ .trim()
213
+ .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens
214
+ .replace(/^-+|-+$/g, ''); // Remove leading/trailing hyphens
215
+ }
216
+
217
+ /**
218
+ * Decode a URL slug back to title (approximate)
219
+ * @param {string} slug - URL slug
220
+ * @returns {string} Decoded title
221
+ */
222
+ _decodeTitle(slug) {
223
+ if (!slug) return '';
224
+ return slug
225
+ .split('-')
226
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
227
+ .join(' ');
228
+ }
229
+
230
+ /**
231
+ * Generate URL from chain ID, factory title, instance name, and optional piece title
232
+ * @param {string|number} chainId - Chain ID (e.g., 1 for Ethereum mainnet)
233
+ * @param {string} factoryTitle - Factory title
234
+ * @param {string} instanceName - Instance name
235
+ * @param {string} [pieceTitle] - Optional piece title (for ERC1155)
236
+ * @returns {string} URL path
237
+ */
238
+ generateURL(chainId, factoryTitle, instanceName, pieceTitle = null) {
239
+ const chainIdStr = String(chainId || '1'); // Default to 1 (Ethereum mainnet)
240
+ const factorySlug = this._encodeTitle(factoryTitle);
241
+ const instanceSlug = this._encodeTitle(instanceName);
242
+
243
+ if (pieceTitle) {
244
+ const pieceSlug = this._encodeTitle(pieceTitle);
245
+ return `/${chainIdStr}/${factorySlug}/${instanceSlug}/${pieceSlug}`;
246
+ }
247
+
248
+ return `/${chainIdStr}/${factorySlug}/${instanceSlug}`;
249
+ }
250
+ }
251
+
252
+ export default Router;
253
+