@nuraly/lumenjs 0.1.3 → 0.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 (306) hide show
  1. package/README.md +62 -282
  2. package/dist/auth/config.d.ts +23 -0
  3. package/dist/auth/config.js +115 -0
  4. package/dist/auth/guard.d.ts +12 -0
  5. package/dist/auth/guard.js +28 -0
  6. package/dist/auth/index.d.ts +3 -0
  7. package/dist/auth/index.js +1 -0
  8. package/dist/auth/middleware.d.ts +23 -0
  9. package/dist/auth/middleware.js +89 -0
  10. package/dist/auth/native-auth.d.ts +82 -0
  11. package/dist/auth/native-auth.js +340 -0
  12. package/dist/auth/oidc-client.d.ts +17 -0
  13. package/dist/auth/oidc-client.js +123 -0
  14. package/dist/auth/providers/google.d.ts +23 -0
  15. package/dist/auth/providers/google.js +25 -0
  16. package/dist/auth/providers/index.d.ts +2 -0
  17. package/dist/auth/providers/index.js +1 -0
  18. package/dist/auth/routes/login.d.ts +8 -0
  19. package/dist/auth/routes/login.js +121 -0
  20. package/dist/auth/routes/logout.d.ts +4 -0
  21. package/dist/auth/routes/logout.js +79 -0
  22. package/dist/auth/routes/oidc-callback.d.ts +3 -0
  23. package/dist/auth/routes/oidc-callback.js +70 -0
  24. package/dist/auth/routes/password.d.ts +5 -0
  25. package/dist/auth/routes/password.js +149 -0
  26. package/dist/auth/routes/signup.d.ts +3 -0
  27. package/dist/auth/routes/signup.js +81 -0
  28. package/dist/auth/routes/token.d.ts +4 -0
  29. package/dist/auth/routes/token.js +70 -0
  30. package/dist/auth/routes/totp.d.ts +22 -0
  31. package/dist/auth/routes/totp.js +232 -0
  32. package/dist/auth/routes/utils.d.ts +7 -0
  33. package/dist/auth/routes/utils.js +35 -0
  34. package/dist/auth/routes/verify.d.ts +3 -0
  35. package/dist/auth/routes/verify.js +26 -0
  36. package/dist/auth/routes.d.ts +8 -0
  37. package/dist/auth/routes.js +124 -0
  38. package/dist/auth/session.d.ts +8 -0
  39. package/dist/auth/session.js +54 -0
  40. package/dist/auth/token.d.ts +33 -0
  41. package/dist/auth/token.js +90 -0
  42. package/dist/auth/types.d.ts +156 -0
  43. package/dist/auth/types.js +2 -0
  44. package/dist/build/build-client.d.ts +15 -0
  45. package/dist/build/build-client.js +45 -0
  46. package/dist/build/build-prerender.d.ts +11 -0
  47. package/dist/build/build-prerender.js +159 -0
  48. package/dist/build/build-server.d.ts +18 -0
  49. package/dist/build/build-server.js +107 -0
  50. package/dist/build/build.js +60 -123
  51. package/dist/build/scan.d.ts +18 -0
  52. package/dist/build/scan.js +77 -6
  53. package/dist/build/serve-api.js +8 -2
  54. package/dist/build/serve-loaders.d.ts +4 -4
  55. package/dist/build/serve-loaders.js +26 -18
  56. package/dist/build/serve-ssr.js +38 -11
  57. package/dist/build/serve-static.js +3 -3
  58. package/dist/build/serve.js +341 -18
  59. package/dist/cli.js +37 -6
  60. package/dist/communication/encryption.d.ts +35 -0
  61. package/dist/communication/encryption.js +90 -0
  62. package/dist/communication/handlers/context.d.ts +27 -0
  63. package/dist/communication/handlers/context.js +1 -0
  64. package/dist/communication/handlers/conversation.d.ts +24 -0
  65. package/dist/communication/handlers/conversation.js +113 -0
  66. package/dist/communication/handlers/file-upload.d.ts +17 -0
  67. package/dist/communication/handlers/file-upload.js +62 -0
  68. package/dist/communication/handlers/messaging.d.ts +30 -0
  69. package/dist/communication/handlers/messaging.js +237 -0
  70. package/dist/communication/handlers/presence.d.ts +15 -0
  71. package/dist/communication/handlers/presence.js +76 -0
  72. package/dist/communication/handlers.d.ts +5 -0
  73. package/dist/communication/handlers.js +5 -0
  74. package/dist/communication/index.d.ts +9 -0
  75. package/dist/communication/index.js +7 -0
  76. package/dist/communication/link-preview.d.ts +18 -0
  77. package/dist/communication/link-preview.js +115 -0
  78. package/dist/communication/schema.d.ts +10 -0
  79. package/dist/communication/schema.js +101 -0
  80. package/dist/communication/server.d.ts +86 -0
  81. package/dist/communication/server.js +212 -0
  82. package/dist/communication/signaling.d.ts +43 -0
  83. package/dist/communication/signaling.js +271 -0
  84. package/dist/communication/store.d.ts +71 -0
  85. package/dist/communication/store.js +289 -0
  86. package/dist/communication/types.d.ts +454 -0
  87. package/dist/communication/types.js +1 -0
  88. package/dist/create.d.ts +1 -0
  89. package/dist/create.js +55 -0
  90. package/dist/db/auto-migrate.d.ts +3 -0
  91. package/dist/db/auto-migrate.js +100 -0
  92. package/dist/db/client.d.ts +3 -0
  93. package/dist/db/client.js +18 -0
  94. package/dist/db/index.d.ts +17 -13
  95. package/dist/db/index.js +205 -26
  96. package/dist/db/seed.d.ts +12 -0
  97. package/dist/db/seed.js +88 -0
  98. package/dist/db/table.d.ts +10 -0
  99. package/dist/db/table.js +12 -0
  100. package/dist/dev-server/config.d.ts +11 -0
  101. package/dist/dev-server/config.js +40 -20
  102. package/dist/dev-server/index-html.d.ts +4 -0
  103. package/dist/dev-server/index-html.js +21 -6
  104. package/dist/dev-server/nuralyui-aliases.d.ts +0 -4
  105. package/dist/dev-server/nuralyui-aliases.js +115 -94
  106. package/dist/dev-server/plugins/vite-plugin-api-routes.js +29 -5
  107. package/dist/dev-server/plugins/vite-plugin-auth.d.ts +6 -0
  108. package/dist/dev-server/plugins/vite-plugin-auth.js +223 -0
  109. package/dist/dev-server/plugins/vite-plugin-auto-define.d.ts +16 -0
  110. package/dist/dev-server/plugins/vite-plugin-auto-define.js +111 -0
  111. package/dist/dev-server/plugins/vite-plugin-communication.d.ts +6 -0
  112. package/dist/dev-server/plugins/vite-plugin-communication.js +205 -0
  113. package/dist/dev-server/plugins/vite-plugin-editor-api.d.ts +6 -0
  114. package/dist/dev-server/plugins/vite-plugin-editor-api.js +318 -0
  115. package/dist/dev-server/plugins/vite-plugin-i18n.js +69 -2
  116. package/dist/dev-server/plugins/vite-plugin-lit-dedup.d.ts +6 -0
  117. package/dist/dev-server/plugins/vite-plugin-lit-dedup.js +78 -34
  118. package/dist/dev-server/plugins/vite-plugin-lit-hmr.js +44 -2
  119. package/dist/dev-server/plugins/vite-plugin-llms.d.ts +2 -0
  120. package/dist/dev-server/plugins/vite-plugin-llms.js +92 -0
  121. package/dist/dev-server/plugins/vite-plugin-loaders.js +146 -13
  122. package/dist/dev-server/plugins/vite-plugin-routes.js +16 -5
  123. package/dist/dev-server/plugins/vite-plugin-socketio.d.ts +2 -0
  124. package/dist/dev-server/plugins/vite-plugin-socketio.js +51 -0
  125. package/dist/dev-server/plugins/vite-plugin-source-annotator.d.ts +2 -0
  126. package/dist/dev-server/plugins/vite-plugin-source-annotator.js +26 -3
  127. package/dist/dev-server/plugins/vite-plugin-storage.d.ts +10 -0
  128. package/dist/dev-server/plugins/vite-plugin-storage.js +126 -0
  129. package/dist/dev-server/plugins/vite-plugin-virtual-modules.js +140 -3
  130. package/dist/dev-server/server.js +242 -70
  131. package/dist/dev-server/ssr-render.d.ts +2 -1
  132. package/dist/dev-server/ssr-render.js +117 -50
  133. package/dist/editor/ai/backend.d.ts +20 -0
  134. package/dist/editor/ai/backend.js +113 -0
  135. package/dist/editor/ai/claude-code-client.d.ts +20 -0
  136. package/dist/editor/ai/claude-code-client.js +145 -0
  137. package/dist/editor/ai/deepseek-client.d.ts +7 -0
  138. package/dist/editor/ai/deepseek-client.js +113 -0
  139. package/dist/editor/ai/opencode-client.d.ts +14 -0
  140. package/dist/editor/ai/opencode-client.js +99 -0
  141. package/dist/editor/ai/snapshot-store.d.ts +22 -0
  142. package/dist/editor/ai/snapshot-store.js +35 -0
  143. package/dist/editor/ai/types.d.ts +30 -0
  144. package/dist/editor/ai/types.js +136 -0
  145. package/dist/editor/ai-chat-panel.d.ts +13 -0
  146. package/dist/editor/ai-chat-panel.js +613 -0
  147. package/dist/editor/ai-markdown.d.ts +10 -0
  148. package/dist/editor/ai-markdown.js +70 -0
  149. package/dist/editor/ai-project-panel.d.ts +11 -0
  150. package/dist/editor/ai-project-panel.js +332 -0
  151. package/dist/editor/ast-modification.d.ts +11 -0
  152. package/dist/editor/ast-modification.js +1 -0
  153. package/dist/editor/ast-service.d.ts +30 -0
  154. package/dist/editor/ast-service.js +180 -0
  155. package/dist/editor/css-rules.d.ts +54 -0
  156. package/dist/editor/css-rules.js +423 -0
  157. package/dist/editor/editor-api-client.d.ts +51 -0
  158. package/dist/editor/editor-api-client.js +162 -0
  159. package/dist/editor/editor-bridge.d.ts +1 -0
  160. package/dist/editor/editor-bridge.js +18 -8
  161. package/dist/editor/editor-toolbar.d.ts +14 -0
  162. package/dist/editor/editor-toolbar.js +115 -0
  163. package/dist/editor/file-editor.d.ts +9 -0
  164. package/dist/editor/file-editor.js +236 -0
  165. package/dist/editor/file-service.d.ts +16 -0
  166. package/dist/editor/file-service.js +52 -0
  167. package/dist/editor/i18n-key-gen.d.ts +1 -0
  168. package/dist/editor/i18n-key-gen.js +7 -0
  169. package/dist/editor/inline-text-edit.d.ts +5 -0
  170. package/dist/editor/inline-text-edit.js +173 -92
  171. package/dist/editor/overlay-events.d.ts +5 -0
  172. package/dist/editor/overlay-events.js +364 -0
  173. package/dist/editor/overlay-hmr.d.ts +2 -0
  174. package/dist/editor/overlay-hmr.js +76 -0
  175. package/dist/editor/overlay-selection.d.ts +29 -0
  176. package/dist/editor/overlay-selection.js +148 -0
  177. package/dist/editor/overlay-utils.d.ts +12 -0
  178. package/dist/editor/overlay-utils.js +59 -0
  179. package/dist/editor/properties-panel-persist.d.ts +14 -0
  180. package/dist/editor/properties-panel-persist.js +70 -0
  181. package/dist/editor/properties-panel-rows.d.ts +10 -0
  182. package/dist/editor/properties-panel-rows.js +349 -0
  183. package/dist/editor/properties-panel-styles.d.ts +4 -0
  184. package/dist/editor/properties-panel-styles.js +174 -0
  185. package/dist/editor/properties-panel.d.ts +4 -0
  186. package/dist/editor/properties-panel.js +148 -0
  187. package/dist/editor/property-registry.d.ts +16 -0
  188. package/dist/editor/property-registry.js +303 -0
  189. package/dist/editor/standalone-file-panel.d.ts +0 -0
  190. package/dist/editor/standalone-file-panel.js +1 -0
  191. package/dist/editor/standalone-overlay-dom.d.ts +0 -0
  192. package/dist/editor/standalone-overlay-dom.js +1 -0
  193. package/dist/editor/standalone-overlay-styles.d.ts +0 -0
  194. package/dist/editor/standalone-overlay-styles.js +1 -0
  195. package/dist/editor/standalone-overlay.d.ts +1 -0
  196. package/dist/editor/standalone-overlay.js +76 -0
  197. package/dist/editor/syntax-highlighter.d.ts +4 -0
  198. package/dist/editor/syntax-highlighter.js +81 -0
  199. package/dist/editor/text-toolbar.d.ts +11 -0
  200. package/dist/editor/text-toolbar.js +327 -0
  201. package/dist/editor/toolbar-styles.d.ts +4 -0
  202. package/dist/editor/toolbar-styles.js +198 -0
  203. package/dist/email/index.d.ts +32 -0
  204. package/dist/email/index.js +154 -0
  205. package/dist/email/providers/resend.d.ts +2 -0
  206. package/dist/email/providers/resend.js +24 -0
  207. package/dist/email/providers/sendgrid.d.ts +2 -0
  208. package/dist/email/providers/sendgrid.js +31 -0
  209. package/dist/email/providers/smtp.d.ts +13 -0
  210. package/dist/email/providers/smtp.js +125 -0
  211. package/dist/email/template-engine.d.ts +18 -0
  212. package/dist/email/template-engine.js +116 -0
  213. package/dist/email/templates/base.d.ts +9 -0
  214. package/dist/email/templates/base.js +65 -0
  215. package/dist/email/templates/password-reset.d.ts +5 -0
  216. package/dist/email/templates/password-reset.js +15 -0
  217. package/dist/email/templates/verify-email.d.ts +5 -0
  218. package/dist/email/templates/verify-email.js +15 -0
  219. package/dist/email/templates/welcome.d.ts +5 -0
  220. package/dist/email/templates/welcome.js +13 -0
  221. package/dist/email/types.d.ts +49 -0
  222. package/dist/email/types.js +1 -0
  223. package/dist/llms/generate.d.ts +46 -0
  224. package/dist/llms/generate.js +185 -0
  225. package/dist/permissions/guard.d.ts +28 -0
  226. package/dist/permissions/guard.js +30 -0
  227. package/dist/permissions/index.d.ts +6 -0
  228. package/dist/permissions/index.js +3 -0
  229. package/dist/permissions/service.d.ts +80 -0
  230. package/dist/permissions/service.js +210 -0
  231. package/dist/permissions/tables.d.ts +5 -0
  232. package/dist/permissions/tables.js +68 -0
  233. package/dist/permissions/types.d.ts +33 -0
  234. package/dist/permissions/types.js +1 -0
  235. package/dist/runtime/app-shell.d.ts +1 -1
  236. package/dist/runtime/app-shell.js +164 -0
  237. package/dist/runtime/auth.d.ts +10 -0
  238. package/dist/runtime/auth.js +30 -0
  239. package/dist/runtime/communication.d.ts +137 -0
  240. package/dist/runtime/communication.js +228 -0
  241. package/dist/runtime/error-boundary.d.ts +23 -0
  242. package/dist/runtime/error-boundary.js +120 -0
  243. package/dist/runtime/i18n.d.ts +6 -1
  244. package/dist/runtime/i18n.js +42 -21
  245. package/dist/runtime/island.d.ts +16 -0
  246. package/dist/runtime/island.js +80 -0
  247. package/dist/runtime/router-data.d.ts +3 -0
  248. package/dist/runtime/router-data.js +102 -17
  249. package/dist/runtime/router-hydration.js +34 -2
  250. package/dist/runtime/router.d.ts +19 -2
  251. package/dist/runtime/router.js +237 -43
  252. package/dist/runtime/socket-client.d.ts +2 -0
  253. package/dist/runtime/socket-client.js +30 -0
  254. package/dist/runtime/webrtc.d.ts +91 -0
  255. package/dist/runtime/webrtc.js +428 -0
  256. package/dist/shared/dom-shims.js +4 -2
  257. package/dist/shared/graceful-shutdown.d.ts +8 -0
  258. package/dist/shared/graceful-shutdown.js +36 -0
  259. package/dist/shared/health.d.ts +8 -0
  260. package/dist/shared/health.js +25 -0
  261. package/dist/shared/llms-txt.d.ts +31 -0
  262. package/dist/shared/llms-txt.js +85 -0
  263. package/dist/shared/logger.d.ts +32 -0
  264. package/dist/shared/logger.js +93 -0
  265. package/dist/shared/meta.d.ts +27 -0
  266. package/dist/shared/meta.js +71 -0
  267. package/dist/shared/middleware-runner.d.ts +9 -0
  268. package/dist/shared/middleware-runner.js +29 -0
  269. package/dist/shared/rate-limit.d.ts +18 -0
  270. package/dist/shared/rate-limit.js +71 -0
  271. package/dist/shared/request-id.d.ts +5 -0
  272. package/dist/shared/request-id.js +18 -0
  273. package/dist/shared/route-matching.js +16 -1
  274. package/dist/shared/security-headers.d.ts +18 -0
  275. package/dist/shared/security-headers.js +38 -0
  276. package/dist/shared/socket-io-setup.d.ts +11 -0
  277. package/dist/shared/socket-io-setup.js +51 -0
  278. package/dist/shared/types.d.ts +15 -0
  279. package/dist/shared/utils.d.ts +33 -7
  280. package/dist/shared/utils.js +164 -27
  281. package/dist/storage/adapters/local.d.ts +44 -0
  282. package/dist/storage/adapters/local.js +85 -0
  283. package/dist/storage/adapters/s3.d.ts +32 -0
  284. package/dist/storage/adapters/s3.js +119 -0
  285. package/dist/storage/adapters/types.d.ts +53 -0
  286. package/dist/storage/adapters/types.js +1 -0
  287. package/dist/storage/index.d.ts +76 -0
  288. package/dist/storage/index.js +83 -0
  289. package/package.json +45 -7
  290. package/templates/blog/api/posts.ts +4 -18
  291. package/templates/blog/data/migrations/001_init.sql +6 -5
  292. package/templates/blog/lumenjs.config.ts +3 -0
  293. package/templates/blog/package.json +14 -0
  294. package/templates/blog/pages/_layout.ts +25 -0
  295. package/templates/blog/pages/index.ts +48 -22
  296. package/templates/blog/pages/posts/[slug].ts +45 -20
  297. package/templates/blog/pages/tag/[tag].ts +44 -0
  298. package/templates/dashboard/api/stats.ts +8 -5
  299. package/templates/dashboard/lumenjs.config.ts +3 -0
  300. package/templates/dashboard/package.json +14 -0
  301. package/templates/dashboard/pages/_layout.ts +25 -0
  302. package/templates/dashboard/pages/index.ts +54 -23
  303. package/templates/dashboard/pages/settings/index.ts +29 -0
  304. package/templates/default/lumenjs.config.ts +3 -0
  305. package/templates/default/package.json +14 -0
  306. package/templates/default/pages/index.ts +24 -0
@@ -1,18 +1,175 @@
1
1
  import { sendToHost, isPreviewMode } from './editor-bridge.js';
2
+ import { applyAstModification, updateTranslation } from './editor-api-client.js';
3
+ const EDITABLE_TAGS = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LABEL', 'LI'];
4
+ let editingEl = null;
5
+ /**
6
+ * Find the text element and annotated parent from a target element,
7
+ * walking up the DOM tree. Works for both real events and direct calls.
8
+ */
9
+ function findEditTarget(startEl) {
10
+ let textEl = null;
11
+ let annotatedParent = null;
12
+ let el = startEl;
13
+ while (el) {
14
+ if (!textEl && EDITABLE_TAGS.includes(el.tagName)) {
15
+ textEl = el;
16
+ }
17
+ if (!annotatedParent && el.getAttribute('data-nk-source')) {
18
+ annotatedParent = el;
19
+ }
20
+ if (textEl && annotatedParent)
21
+ break;
22
+ el = el.parentElement;
23
+ }
24
+ // If no direct text element, check if start element has only text content
25
+ if (!textEl) {
26
+ if (startEl.childNodes.length > 0) {
27
+ const hasOnlyText = Array.from(startEl.childNodes).every(n => n.nodeType === Node.TEXT_NODE);
28
+ if (hasOnlyText && startEl.textContent?.trim()) {
29
+ textEl = startEl;
30
+ }
31
+ }
32
+ }
33
+ if (!textEl || !annotatedParent)
34
+ return null;
35
+ return { textEl, annotatedParent };
36
+ }
37
+ function showDynamicWarning(textEl) {
38
+ textEl.style.outline = '2px dashed #f59e0b';
39
+ textEl.style.outlineOffset = '2px';
40
+ const indicator = document.createElement('div');
41
+ Object.assign(indicator.style, {
42
+ position: 'fixed',
43
+ background: '#f59e0b',
44
+ color: '#000',
45
+ padding: '4px 8px',
46
+ borderRadius: '4px',
47
+ fontSize: '11px',
48
+ fontFamily: 'system-ui',
49
+ zIndex: '10000',
50
+ pointerEvents: 'none',
51
+ });
52
+ indicator.textContent = '\u26A1 Bound to variable \u2014 edit in code';
53
+ const rect = textEl.getBoundingClientRect();
54
+ indicator.style.left = `${rect.left}px`;
55
+ indicator.style.top = `${rect.top - 28}px`;
56
+ document.body.appendChild(indicator);
57
+ setTimeout(() => {
58
+ textEl.style.outline = '';
59
+ textEl.style.outlineOffset = '';
60
+ indicator.remove();
61
+ }, 2000);
62
+ }
63
+ function startEditing(textEl, annotatedParent) {
64
+ if (editingEl)
65
+ return;
66
+ editingEl = textEl;
67
+ const originalText = textEl.textContent || '';
68
+ textEl.setAttribute('contenteditable', 'true');
69
+ textEl.focus();
70
+ textEl.style.outline = '2px solid #3b82f6';
71
+ textEl.style.outlineOffset = '2px';
72
+ textEl.style.borderRadius = '2px';
73
+ textEl.style.minWidth = '20px';
74
+ const range = document.createRange();
75
+ range.selectNodeContents(textEl);
76
+ const sel = window.getSelection();
77
+ sel?.removeAllRanges();
78
+ sel?.addRange(range);
79
+ const sourceAttr = annotatedParent.getAttribute('data-nk-source');
80
+ const lastColon = sourceAttr.lastIndexOf(':');
81
+ const sourceFile = sourceAttr.substring(0, lastColon);
82
+ const line = parseInt(sourceAttr.substring(lastColon + 1), 10);
83
+ const commitEdit = () => {
84
+ if (!editingEl)
85
+ return;
86
+ const newText = editingEl.textContent || '';
87
+ editingEl.removeAttribute('contenteditable');
88
+ editingEl.style.outline = '';
89
+ editingEl.style.outlineOffset = '';
90
+ editingEl.style.borderRadius = '';
91
+ editingEl.style.minWidth = '';
92
+ editingEl = null;
93
+ if (newText !== originalText) {
94
+ const i18nKey = textEl.getAttribute('data-nk-i18n-key');
95
+ if (i18nKey) {
96
+ const locale = document.documentElement.lang || 'en';
97
+ updateTranslation(locale, i18nKey, newText).then(() => {
98
+ sendToHost({
99
+ type: 'NK_TRANSLATION_CHANGED',
100
+ payload: { key: i18nKey, locale, originalText, newText, appliedLocally: true }
101
+ });
102
+ }).catch(() => {
103
+ sendToHost({
104
+ type: 'NK_TRANSLATION_CHANGED',
105
+ payload: { key: i18nKey, locale, originalText, newText }
106
+ });
107
+ });
108
+ }
109
+ else {
110
+ applyAstModification(sourceFile, {
111
+ type: 'setTextContent',
112
+ elementSelector: textEl.tagName.toLowerCase(),
113
+ sourceLine: line,
114
+ html: newText,
115
+ }).then(() => {
116
+ sendToHost({
117
+ type: 'NK_TEXT_CHANGED',
118
+ payload: { sourceFile, line, originalText, newText, appliedLocally: true }
119
+ });
120
+ }).catch(() => {
121
+ sendToHost({
122
+ type: 'NK_TEXT_CHANGED',
123
+ payload: { sourceFile, line, originalText, newText }
124
+ });
125
+ });
126
+ }
127
+ }
128
+ };
129
+ textEl.addEventListener('blur', commitEdit, { once: true });
130
+ textEl.addEventListener('keydown', (e) => {
131
+ if (e.key === 'Enter' && !e.shiftKey) {
132
+ e.preventDefault();
133
+ textEl.blur();
134
+ }
135
+ if (e.key === 'Escape') {
136
+ textEl.textContent = originalText;
137
+ textEl.blur();
138
+ }
139
+ });
140
+ }
141
+ /**
142
+ * Trigger inline text editing on an element directly.
143
+ * Called from standalone-overlay.ts on double-tap (mobile).
144
+ */
145
+ export function triggerInlineEdit(target) {
146
+ if (isPreviewMode() || editingEl)
147
+ return false;
148
+ const result = findEditTarget(target);
149
+ if (!result)
150
+ return false;
151
+ const { textEl, annotatedParent } = result;
152
+ const isDynamic = textEl.hasAttribute('data-nk-dynamic') || !!textEl.closest('[data-nk-dynamic]');
153
+ const isI18n = !!textEl.getAttribute('data-nk-i18n-key') || !!textEl.closest('[data-nk-i18n-key]');
154
+ if (isDynamic && !isI18n) {
155
+ showDynamicWarning(textEl);
156
+ return true;
157
+ }
158
+ startEditing(textEl, annotatedParent);
159
+ return true;
160
+ }
2
161
  export function setupInlineTextEdit() {
3
- let editingEl = null;
4
162
  document.addEventListener('dblclick', (event) => {
5
163
  if (isPreviewMode())
6
164
  return;
7
- // Walk the composed path to find a text-bearing element
8
- const editableTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LABEL', 'LI'];
165
+ // Try composedPath first (works in shadow DOM with real events)
9
166
  const composedPath = event.composedPath();
10
167
  let textEl = null;
11
168
  let annotatedParent = null;
12
169
  for (const node of composedPath) {
13
170
  if (!(node instanceof HTMLElement))
14
171
  continue;
15
- if (!textEl && editableTags.includes(node.tagName)) {
172
+ if (!textEl && EDITABLE_TAGS.includes(node.tagName)) {
16
173
  textEl = node;
17
174
  }
18
175
  if (!annotatedParent && node.getAttribute('data-nk-source')) {
@@ -21,105 +178,29 @@ export function setupInlineTextEdit() {
21
178
  if (textEl && annotatedParent)
22
179
  break;
23
180
  }
24
- // If no direct text element, check if target itself has only text content
25
- if (!textEl) {
181
+ // Fallback: walk from event.target
182
+ if (!textEl || !annotatedParent) {
26
183
  const target = event.target;
27
- if (target && target.childNodes.length > 0) {
28
- const hasOnlyText = Array.from(target.childNodes).every(n => n.nodeType === Node.TEXT_NODE);
29
- if (hasOnlyText && target.textContent?.trim()) {
30
- textEl = target;
184
+ if (target) {
185
+ const result = findEditTarget(target);
186
+ if (result) {
187
+ textEl = result.textEl;
188
+ annotatedParent = result.annotatedParent;
31
189
  }
32
190
  }
33
191
  }
34
192
  if (!textEl || !annotatedParent || editingEl)
35
193
  return;
36
- // Block inline editing for elements bound to dynamic expressions
37
- if (textEl.hasAttribute('data-nk-dynamic') || textEl.closest('[data-nk-dynamic]')) {
194
+ const isDynamic = textEl.hasAttribute('data-nk-dynamic') || !!textEl.closest('[data-nk-dynamic]');
195
+ const isI18n = !!textEl.getAttribute('data-nk-i18n-key') || !!textEl.closest('[data-nk-i18n-key]');
196
+ if (isDynamic && !isI18n) {
38
197
  event.preventDefault();
39
198
  event.stopPropagation();
40
- textEl.style.outline = '2px dashed #f59e0b';
41
- textEl.style.outlineOffset = '2px';
42
- const indicator = document.createElement('div');
43
- Object.assign(indicator.style, {
44
- position: 'fixed',
45
- background: '#f59e0b',
46
- color: '#000',
47
- padding: '4px 8px',
48
- borderRadius: '4px',
49
- fontSize: '11px',
50
- fontFamily: 'system-ui',
51
- zIndex: '10000',
52
- pointerEvents: 'none',
53
- });
54
- indicator.textContent = '\u26A1 Bound to variable \u2014 edit in code';
55
- const rect = textEl.getBoundingClientRect();
56
- indicator.style.left = `${rect.left}px`;
57
- indicator.style.top = `${rect.top - 28}px`;
58
- document.body.appendChild(indicator);
59
- setTimeout(() => {
60
- textEl.style.outline = '';
61
- textEl.style.outlineOffset = '';
62
- indicator.remove();
63
- }, 2000);
199
+ showDynamicWarning(textEl);
64
200
  return;
65
201
  }
66
202
  event.preventDefault();
67
203
  event.stopPropagation();
68
- editingEl = textEl;
69
- const originalText = textEl.textContent || '';
70
- textEl.setAttribute('contenteditable', 'true');
71
- textEl.focus();
72
- textEl.style.outline = '2px solid #3b82f6';
73
- textEl.style.outlineOffset = '2px';
74
- textEl.style.borderRadius = '2px';
75
- textEl.style.minWidth = '20px';
76
- const range = document.createRange();
77
- range.selectNodeContents(textEl);
78
- const sel = window.getSelection();
79
- sel?.removeAllRanges();
80
- sel?.addRange(range);
81
- const sourceAttr = annotatedParent.getAttribute('data-nk-source');
82
- const lastColon = sourceAttr.lastIndexOf(':');
83
- const sourceFile = sourceAttr.substring(0, lastColon);
84
- const line = parseInt(sourceAttr.substring(lastColon + 1), 10);
85
- const commitEdit = () => {
86
- if (!editingEl)
87
- return;
88
- const newText = editingEl.textContent || '';
89
- editingEl.removeAttribute('contenteditable');
90
- editingEl.style.outline = '';
91
- editingEl.style.outlineOffset = '';
92
- editingEl.style.borderRadius = '';
93
- editingEl.style.minWidth = '';
94
- editingEl = null;
95
- if (newText !== originalText) {
96
- // Check if this element has an i18n key — if so, send a translation change
97
- const i18nKey = textEl.getAttribute('data-nk-i18n-key');
98
- if (i18nKey) {
99
- const locale = document.documentElement.lang || 'en';
100
- sendToHost({
101
- type: 'NK_TRANSLATION_CHANGED',
102
- payload: { key: i18nKey, locale, originalText, newText }
103
- });
104
- }
105
- else {
106
- sendToHost({
107
- type: 'NK_TEXT_CHANGED',
108
- payload: { sourceFile, line, originalText, newText }
109
- });
110
- }
111
- }
112
- };
113
- textEl.addEventListener('blur', commitEdit, { once: true });
114
- textEl.addEventListener('keydown', (e) => {
115
- if (e.key === 'Enter' && !e.shiftKey) {
116
- e.preventDefault();
117
- textEl.blur();
118
- }
119
- if (e.key === 'Escape') {
120
- textEl.textContent = originalText;
121
- textEl.blur();
122
- }
123
- });
204
+ startEditing(textEl, annotatedParent);
124
205
  });
125
206
  }
@@ -0,0 +1,5 @@
1
+ export declare function setupMouseEvents(): void;
2
+ export declare function setupTouchEvents(): void;
3
+ export declare function setupToolbarHandlers(toolbar: HTMLDivElement, filePanel: HTMLDivElement): void;
4
+ export declare function setupKeyboardHandlers(toolbar: HTMLDivElement): void;
5
+ export declare function setupScrollResize(): void;
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Overlay Events — mouse, touch, toolbar, keyboard, and scroll/resize
3
+ * event handlers for the standalone editor overlay.
4
+ */
5
+ import { findAnnotatedElement, parseSourceAttr } from './element-annotator.js';
6
+ import { triggerInlineEdit } from './inline-text-edit.js';
7
+ import { deepElementFromPoint, positionOverlay, hideOverlay } from './overlay-utils.js';
8
+ import { positionTextToolbar, hideTextToolbar } from './text-toolbar.js';
9
+ import { showPropertiesForElement, hidePropertiesPanel, isPropertiesPanelOpen } from './properties-panel.js';
10
+ import { hideAiChatPanel, isAiChatPanelOpen, updateAiChatPosition } from './ai-chat-panel.js';
11
+ import { showAiProjectPanel, hideAiProjectPanel, isAiProjectPanelOpen } from './ai-project-panel.js';
12
+ import { showTextToolbarForElement } from './text-toolbar.js';
13
+ import { showAiChatForElement } from './ai-chat-panel.js';
14
+ import { updateSelectionInfo, setMode, closeFilePanel, loadFileList, saveCurrentFile, getIsEditorMode, getCurrentEditorFile, getIsFilePanelOpen, setIsFilePanelOpen, } from './editor-toolbar.js';
15
+ import { getSelectedElement, setSelectedElement, getHoverOverlay, getSelectOverlay, getMultiSelectedElements, getMultiSelectOverlays, deselect, selectSingle, toggleMultiSelect, touchToElement, sendPageAiPrompt, } from './overlay-selection.js';
16
+ const isTouchDevice = ('ontouchstart' in window) || navigator.maxTouchPoints > 0;
17
+ // ── Mouse events (desktop) ──
18
+ export function setupMouseEvents() {
19
+ const hoverOverlay = getHoverOverlay();
20
+ let lastHoverEl = null;
21
+ let hoverRaf = 0;
22
+ document.addEventListener('mousemove', (event) => {
23
+ if (!getIsEditorMode())
24
+ return;
25
+ if (hoverRaf)
26
+ return;
27
+ hoverRaf = requestAnimationFrame(() => {
28
+ hoverRaf = 0;
29
+ const selectedElement = getSelectedElement();
30
+ let hoverEl = document.elementFromPoint(event.clientX, event.clientY);
31
+ while (hoverEl?.shadowRoot) {
32
+ const inner = hoverEl.shadowRoot.elementFromPoint(event.clientX, event.clientY);
33
+ if (!inner || inner === hoverEl)
34
+ break;
35
+ hoverEl = inner;
36
+ }
37
+ if (hoverEl && hoverEl !== selectedElement && hoverEl !== document.body && hoverEl !== document.documentElement && !hoverEl.closest('#nk-editor-toolbar') && !hoverEl.closest('#nk-props-panel') && !hoverEl.closest('#nk-file-panel') && !hoverEl.closest('#nk-ai-chat') && !hoverEl.closest('#nk-ai-project') && !hoverEl.closest('#nk-pp-fab')) {
38
+ if (hoverEl !== lastHoverEl) {
39
+ lastHoverEl = hoverEl;
40
+ positionOverlay(hoverOverlay, hoverEl);
41
+ }
42
+ }
43
+ else {
44
+ lastHoverEl = null;
45
+ hideOverlay(hoverOverlay);
46
+ }
47
+ });
48
+ }, true);
49
+ document.addEventListener('mouseleave', () => {
50
+ lastHoverEl = null;
51
+ hideOverlay(hoverOverlay);
52
+ });
53
+ // Pointerdown handler to select disabled/pointer-events:none elements.
54
+ document.addEventListener('pointerdown', (event) => {
55
+ if (isTouchDevice)
56
+ return;
57
+ if (!getIsEditorMode())
58
+ return;
59
+ const t = event.target;
60
+ if (t.closest('#nk-editor-toolbar') || t.closest('#nk-file-panel') || t.closest('#nk-file-editor') || t.closest('#nk-text-toolbar') || t.closest('#nk-props-panel') || t.closest('#nk-ai-chat') || t.closest('#nk-ai-project') || t.closest('#nk-pp-fab'))
61
+ return;
62
+ const deepEl = deepElementFromPoint(event.clientX, event.clientY);
63
+ if (!deepEl)
64
+ return;
65
+ const deepRoot = deepEl.getRootNode();
66
+ const hostEl = deepRoot instanceof ShadowRoot ? deepRoot.host : null;
67
+ const isHostUnclickable = hostEl && (hostEl.hasAttribute('disabled') ||
68
+ hostEl.getAttribute('aria-disabled') === 'true' ||
69
+ window.getComputedStyle(hostEl).pointerEvents === 'none');
70
+ const isDeepUnclickable = deepEl.disabled ||
71
+ window.getComputedStyle(deepEl).pointerEvents === 'none';
72
+ if (!isHostUnclickable && !isDeepUnclickable)
73
+ return;
74
+ let selectEl = hostEl && hostEl.getAttribute('data-nk-source') ? hostEl : deepEl;
75
+ if (!selectEl.getAttribute('data-nk-source')) {
76
+ let el = selectEl;
77
+ while (el) {
78
+ if (el.getAttribute('data-nk-source')) {
79
+ selectEl = el;
80
+ break;
81
+ }
82
+ const r = el.getRootNode();
83
+ if (r instanceof ShadowRoot) {
84
+ el = r.host;
85
+ continue;
86
+ }
87
+ el = el.parentElement;
88
+ }
89
+ }
90
+ event.preventDefault();
91
+ event.stopPropagation();
92
+ if (event.shiftKey) {
93
+ toggleMultiSelect(selectEl);
94
+ }
95
+ else {
96
+ selectSingle(selectEl);
97
+ }
98
+ }, true);
99
+ // Click to select (desktop) — supports Shift+click for multi-select
100
+ let clickTimer = null;
101
+ document.addEventListener('click', (event) => {
102
+ if (isTouchDevice)
103
+ return;
104
+ if (!getIsEditorMode())
105
+ return;
106
+ const t = event.target;
107
+ if (t.closest('#nk-editor-toolbar') || t.closest('#nk-file-panel') || t.closest('#nk-file-editor') || t.closest('#nk-text-toolbar') || t.closest('#nk-props-panel') || t.closest('#nk-ai-chat') || t.closest('#nk-ai-project') || t.closest('#nk-pp-fab'))
108
+ return;
109
+ let result = findAnnotatedElement(event);
110
+ if (result?.element) {
111
+ const root = result.element.getRootNode();
112
+ if (root instanceof ShadowRoot) {
113
+ const host = root.host;
114
+ const hostSrc = host.getAttribute('data-nk-source');
115
+ if (hostSrc) {
116
+ const parsed = parseSourceAttr(hostSrc);
117
+ if (parsed)
118
+ result = { element: host, source: parsed };
119
+ }
120
+ }
121
+ }
122
+ if (!result && t) {
123
+ let el = t;
124
+ const root = t.getRootNode();
125
+ if (root instanceof ShadowRoot) {
126
+ const host = root.host;
127
+ const hostSrc = host.getAttribute('data-nk-source');
128
+ if (hostSrc) {
129
+ const parsed = parseSourceAttr(hostSrc);
130
+ if (parsed)
131
+ result = { element: host, source: parsed };
132
+ }
133
+ }
134
+ if (!result) {
135
+ while (el) {
136
+ const src = el.getAttribute('data-nk-source');
137
+ if (src) {
138
+ const parsed = parseSourceAttr(src);
139
+ if (parsed) {
140
+ result = { element: el, source: parsed };
141
+ break;
142
+ }
143
+ }
144
+ el = el.parentElement;
145
+ }
146
+ }
147
+ }
148
+ let targetEl = result?.element ?? t;
149
+ if (!targetEl || targetEl === document.body || targetEl === document.documentElement) {
150
+ deselect();
151
+ return;
152
+ }
153
+ event.preventDefault();
154
+ event.stopPropagation();
155
+ if (clickTimer)
156
+ clearTimeout(clickTimer);
157
+ const selectEl = targetEl;
158
+ const isShift = event.shiftKey;
159
+ clickTimer = setTimeout(() => {
160
+ if (isShift) {
161
+ toggleMultiSelect(selectEl);
162
+ }
163
+ else {
164
+ selectSingle(selectEl);
165
+ }
166
+ }, 200);
167
+ }, true);
168
+ document.addEventListener('dblclick', () => {
169
+ if (clickTimer) {
170
+ clearTimeout(clickTimer);
171
+ clickTimer = null;
172
+ }
173
+ }, true);
174
+ }
175
+ // ── Touch events (mobile/tablet) ──
176
+ export function setupTouchEvents() {
177
+ const hoverOverlay = getHoverOverlay();
178
+ const selectOverlay = getSelectOverlay();
179
+ let lastTapTime = 0;
180
+ let lastTapTarget = null;
181
+ let tapTimer = null;
182
+ document.addEventListener('touchend', (event) => {
183
+ const target = event.target;
184
+ if (target.closest('#nk-editor-toolbar') || target.closest('#nk-file-panel') || target.closest('#nk-file-editor') || target.closest('#nk-text-toolbar') || target.closest('#nk-props-panel') || target.closest('#nk-ai-chat') || target.closest('#nk-ai-project') || target.closest('#nk-pp-fab')) {
185
+ return;
186
+ }
187
+ if (!getIsEditorMode())
188
+ return;
189
+ const touch = event.changedTouches[0];
190
+ if (!touch)
191
+ return;
192
+ const result = touchToElement(touch);
193
+ let touchTargetEl = result?.element ?? document.elementFromPoint(touch.clientX, touch.clientY);
194
+ if (!touchTargetEl || touchTargetEl === document.body || touchTargetEl === document.documentElement)
195
+ return;
196
+ const now = Date.now();
197
+ const resultSource = touchTargetEl.getAttribute('data-nk-source') || '';
198
+ const lastSource = lastTapTarget?.getAttribute('data-nk-source') || '';
199
+ const isDoubleTap = (now - lastTapTime < 350) && (resultSource !== '' ? resultSource === lastSource : touchTargetEl === lastTapTarget);
200
+ lastTapTime = now;
201
+ lastTapTarget = touchTargetEl;
202
+ if (isDoubleTap) {
203
+ if (tapTimer) {
204
+ clearTimeout(tapTimer);
205
+ tapTimer = null;
206
+ }
207
+ hideTextToolbar();
208
+ let touchTarget = document.elementFromPoint(touch.clientX, touch.clientY);
209
+ if (touchTarget?.shadowRoot) {
210
+ const inner = touchTarget.shadowRoot.elementFromPoint(touch.clientX, touch.clientY);
211
+ if (inner instanceof HTMLElement)
212
+ touchTarget = inner;
213
+ }
214
+ if (touchTarget) {
215
+ triggerInlineEdit(touchTarget);
216
+ }
217
+ event.preventDefault();
218
+ }
219
+ else {
220
+ if (tapTimer)
221
+ clearTimeout(tapTimer);
222
+ const tapEl = touchTargetEl;
223
+ tapTimer = setTimeout(() => {
224
+ setSelectedElement(tapEl);
225
+ positionOverlay(selectOverlay, tapEl);
226
+ hideOverlay(hoverOverlay);
227
+ updateSelectionInfo(tapEl);
228
+ showTextToolbarForElement(tapEl);
229
+ showPropertiesForElement(tapEl);
230
+ showAiChatForElement(tapEl);
231
+ }, 300);
232
+ event.preventDefault();
233
+ }
234
+ }, { passive: false, capture: true });
235
+ }
236
+ // ── Toolbar button handlers ──
237
+ export function setupToolbarHandlers(toolbar, filePanel) {
238
+ // Page-level AI input
239
+ const pageAiInput = toolbar.querySelector('.nk-tb-page-ai-input');
240
+ const pageAiSend = toolbar.querySelector('.nk-tb-page-ai-send');
241
+ pageAiInput.addEventListener('input', () => {
242
+ pageAiSend.disabled = !pageAiInput.value.trim();
243
+ });
244
+ pageAiInput.addEventListener('keydown', (e) => {
245
+ if (e.key === 'Enter' && pageAiInput.value.trim()) {
246
+ e.preventDefault();
247
+ sendPageAiPrompt(pageAiInput.value.trim());
248
+ pageAiInput.value = '';
249
+ pageAiSend.disabled = true;
250
+ }
251
+ e.stopPropagation();
252
+ });
253
+ pageAiSend.addEventListener('click', (e) => {
254
+ e.stopPropagation();
255
+ if (pageAiInput.value.trim()) {
256
+ sendPageAiPrompt(pageAiInput.value.trim());
257
+ pageAiInput.value = '';
258
+ pageAiSend.disabled = true;
259
+ }
260
+ });
261
+ // Edit / Preview toggle
262
+ document.getElementById('nk-tb-toggle').addEventListener('click', (e) => {
263
+ e.stopPropagation();
264
+ setMode(!getIsEditorMode());
265
+ });
266
+ // Deselect button
267
+ toolbar.querySelector('.nk-tb-deselect').addEventListener('click', (e) => {
268
+ e.stopPropagation();
269
+ deselect();
270
+ });
271
+ // Project AI panel toggle
272
+ toolbar.querySelector('.nk-tb-project-ai').addEventListener('click', (e) => {
273
+ e.stopPropagation();
274
+ if (isAiProjectPanelOpen()) {
275
+ hideAiProjectPanel();
276
+ toolbar.querySelector('.nk-tb-project-ai').classList.remove('active');
277
+ }
278
+ else {
279
+ showAiProjectPanel();
280
+ toolbar.querySelector('.nk-tb-project-ai').classList.add('active');
281
+ }
282
+ });
283
+ // File panel toggle
284
+ toolbar.querySelector('.nk-tb-files').addEventListener('click', (e) => {
285
+ e.stopPropagation();
286
+ if (getIsFilePanelOpen()) {
287
+ closeFilePanel();
288
+ }
289
+ else {
290
+ setIsFilePanelOpen(true);
291
+ filePanel.classList.add('open');
292
+ toolbar.querySelector('.nk-tb-files').classList.add('active');
293
+ loadFileList();
294
+ }
295
+ });
296
+ // Mobile close button in file panel header
297
+ document.getElementById('nk-fp-close').addEventListener('click', (e) => {
298
+ e.stopPropagation();
299
+ closeFilePanel();
300
+ });
301
+ // File editor save / close
302
+ document.getElementById('nk-fe-save').addEventListener('click', saveCurrentFile);
303
+ document.getElementById('nk-fe-close').addEventListener('click', () => {
304
+ document.getElementById('nk-file-editor').classList.remove('open');
305
+ // On mobile, re-show the file list
306
+ if (window.innerWidth <= 640 && getIsFilePanelOpen()) {
307
+ filePanel.classList.add('open');
308
+ }
309
+ });
310
+ }
311
+ // ── Keyboard handlers ──
312
+ export function setupKeyboardHandlers(toolbar) {
313
+ document.addEventListener('keydown', (e) => {
314
+ if ((e.ctrlKey || e.metaKey) && e.key === 's' && getCurrentEditorFile()) {
315
+ e.preventDefault();
316
+ saveCurrentFile();
317
+ }
318
+ if (e.key === 'Escape') {
319
+ if (getCurrentEditorFile()) {
320
+ document.getElementById('nk-file-editor').classList.remove('open');
321
+ }
322
+ else if (isAiProjectPanelOpen()) {
323
+ hideAiProjectPanel();
324
+ toolbar.querySelector('.nk-tb-project-ai')?.classList.remove('active');
325
+ }
326
+ else if (isAiChatPanelOpen()) {
327
+ hideAiChatPanel();
328
+ }
329
+ else if (isPropertiesPanelOpen()) {
330
+ hidePropertiesPanel();
331
+ }
332
+ else if (getIsFilePanelOpen()) {
333
+ closeFilePanel();
334
+ }
335
+ else if (getSelectedElement()) {
336
+ deselect();
337
+ }
338
+ }
339
+ });
340
+ }
341
+ // ── Scroll / resize overlay repositioning ──
342
+ export function setupScrollResize() {
343
+ const selectOverlay = getSelectOverlay();
344
+ const updateOverlays = () => {
345
+ const selectedElement = getSelectedElement();
346
+ if (selectedElement) {
347
+ positionOverlay(selectOverlay, selectedElement);
348
+ const textTb = document.getElementById('nk-text-toolbar');
349
+ if (textTb && textTb.style.display !== 'none')
350
+ positionTextToolbar(selectedElement);
351
+ }
352
+ // Reposition multi-select overlays
353
+ const multiSelectedElements = getMultiSelectedElements();
354
+ const multiSelectOverlays = getMultiSelectOverlays();
355
+ for (let i = 0; i < multiSelectedElements.length; i++) {
356
+ if (multiSelectedElements[i].isConnected && multiSelectOverlays[i]) {
357
+ positionOverlay(multiSelectOverlays[i], multiSelectedElements[i]);
358
+ }
359
+ }
360
+ updateAiChatPosition();
361
+ };
362
+ window.addEventListener('scroll', updateOverlays, true);
363
+ window.addEventListener('resize', updateOverlays);
364
+ }
@@ -0,0 +1,2 @@
1
+ export declare function reselectAfterHmr(): void;
2
+ export declare function setupHmrListener(): void;