@nuasite/cms 0.1.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 (269) hide show
  1. package/README.md +237 -0
  2. package/dist/src/build-processor.d.ts +20 -0
  3. package/dist/src/build-processor.d.ts.map +1 -0
  4. package/dist/src/collection-scanner.d.ts +6 -0
  5. package/dist/src/collection-scanner.d.ts.map +1 -0
  6. package/dist/src/component-registry.d.ts +63 -0
  7. package/dist/src/component-registry.d.ts.map +1 -0
  8. package/dist/src/config.d.ts +24 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/dev-middleware.d.ts +20 -0
  11. package/dist/src/dev-middleware.d.ts.map +1 -0
  12. package/dist/src/editor/ai.d.ts +60 -0
  13. package/dist/src/editor/ai.d.ts.map +1 -0
  14. package/dist/src/editor/api.d.ts +140 -0
  15. package/dist/src/editor/api.d.ts.map +1 -0
  16. package/dist/src/editor/color-utils.d.ts +106 -0
  17. package/dist/src/editor/color-utils.d.ts.map +1 -0
  18. package/dist/src/editor/components/ai-chat.d.ts +11 -0
  19. package/dist/src/editor/components/ai-chat.d.ts.map +1 -0
  20. package/dist/src/editor/components/ai-tooltip.d.ts +12 -0
  21. package/dist/src/editor/components/ai-tooltip.d.ts.map +1 -0
  22. package/dist/src/editor/components/attribute-editor.d.ts +5 -0
  23. package/dist/src/editor/components/attribute-editor.d.ts.map +1 -0
  24. package/dist/src/editor/components/block-editor.d.ts +12 -0
  25. package/dist/src/editor/components/block-editor.d.ts.map +1 -0
  26. package/dist/src/editor/components/collections-browser.d.ts +2 -0
  27. package/dist/src/editor/components/collections-browser.d.ts.map +1 -0
  28. package/dist/src/editor/components/color-toolbar.d.ts +12 -0
  29. package/dist/src/editor/components/color-toolbar.d.ts.map +1 -0
  30. package/dist/src/editor/components/confirm-dialog.d.ts +2 -0
  31. package/dist/src/editor/components/confirm-dialog.d.ts.map +1 -0
  32. package/dist/src/editor/components/create-page-modal.d.ts +2 -0
  33. package/dist/src/editor/components/create-page-modal.d.ts.map +1 -0
  34. package/dist/src/editor/components/editable-highlights.d.ts +9 -0
  35. package/dist/src/editor/components/editable-highlights.d.ts.map +1 -0
  36. package/dist/src/editor/components/error-boundary.d.ts +32 -0
  37. package/dist/src/editor/components/error-boundary.d.ts.map +1 -0
  38. package/dist/src/editor/components/fields.d.ts +75 -0
  39. package/dist/src/editor/components/fields.d.ts.map +1 -0
  40. package/dist/src/editor/components/frontmatter-fields.d.ts +29 -0
  41. package/dist/src/editor/components/frontmatter-fields.d.ts.map +1 -0
  42. package/dist/src/editor/components/highlight-overlay.d.ts +64 -0
  43. package/dist/src/editor/components/highlight-overlay.d.ts.map +1 -0
  44. package/dist/src/editor/components/image-overlay.d.ts +12 -0
  45. package/dist/src/editor/components/image-overlay.d.ts.map +1 -0
  46. package/dist/src/editor/components/markdown-editor-overlay.d.ts +6 -0
  47. package/dist/src/editor/components/markdown-editor-overlay.d.ts.map +1 -0
  48. package/dist/src/editor/components/markdown-inline-editor.d.ts +10 -0
  49. package/dist/src/editor/components/markdown-inline-editor.d.ts.map +1 -0
  50. package/dist/src/editor/components/media-library.d.ts +2 -0
  51. package/dist/src/editor/components/media-library.d.ts.map +1 -0
  52. package/dist/src/editor/components/outline.d.ts +21 -0
  53. package/dist/src/editor/components/outline.d.ts.map +1 -0
  54. package/dist/src/editor/components/redirect-countdown.d.ts +2 -0
  55. package/dist/src/editor/components/redirect-countdown.d.ts.map +1 -0
  56. package/dist/src/editor/components/seo-editor.d.ts +2 -0
  57. package/dist/src/editor/components/seo-editor.d.ts.map +1 -0
  58. package/dist/src/editor/components/text-style-toolbar.d.ts +8 -0
  59. package/dist/src/editor/components/text-style-toolbar.d.ts.map +1 -0
  60. package/dist/src/editor/components/toast/toast-container.d.ts +7 -0
  61. package/dist/src/editor/components/toast/toast-container.d.ts.map +1 -0
  62. package/dist/src/editor/components/toast/toast.d.ts +7 -0
  63. package/dist/src/editor/components/toast/toast.d.ts.map +1 -0
  64. package/dist/src/editor/components/toast/types.d.ts +7 -0
  65. package/dist/src/editor/components/toast/types.d.ts.map +1 -0
  66. package/dist/src/editor/components/toolbar.d.ts +21 -0
  67. package/dist/src/editor/components/toolbar.d.ts.map +1 -0
  68. package/dist/src/editor/config.d.ts +4 -0
  69. package/dist/src/editor/config.d.ts.map +1 -0
  70. package/dist/src/editor/constants.d.ts +101 -0
  71. package/dist/src/editor/constants.d.ts.map +1 -0
  72. package/dist/src/editor/context.d.ts +14 -0
  73. package/dist/src/editor/context.d.ts.map +1 -0
  74. package/dist/src/editor/dom.d.ts +77 -0
  75. package/dist/src/editor/dom.d.ts.map +1 -0
  76. package/dist/src/editor/editor.d.ts +64 -0
  77. package/dist/src/editor/editor.d.ts.map +1 -0
  78. package/dist/src/editor/history.d.ts +20 -0
  79. package/dist/src/editor/history.d.ts.map +1 -0
  80. package/dist/src/editor/hooks/index.d.ts +14 -0
  81. package/dist/src/editor/hooks/index.d.ts.map +1 -0
  82. package/dist/src/editor/hooks/useAIHandlers.d.ts +22 -0
  83. package/dist/src/editor/hooks/useAIHandlers.d.ts.map +1 -0
  84. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts +18 -0
  85. package/dist/src/editor/hooks/useBlockEditorHandlers.d.ts.map +1 -0
  86. package/dist/src/editor/hooks/useElementDetection.d.ts +26 -0
  87. package/dist/src/editor/hooks/useElementDetection.d.ts.map +1 -0
  88. package/dist/src/editor/hooks/useImageHoverDetection.d.ts +12 -0
  89. package/dist/src/editor/hooks/useImageHoverDetection.d.ts.map +1 -0
  90. package/dist/src/editor/hooks/useTextSelection.d.ts +23 -0
  91. package/dist/src/editor/hooks/useTextSelection.d.ts.map +1 -0
  92. package/dist/src/editor/hooks/useTooltipState.d.ts +19 -0
  93. package/dist/src/editor/hooks/useTooltipState.d.ts.map +1 -0
  94. package/dist/src/editor/hooks/utils.d.ts +32 -0
  95. package/dist/src/editor/hooks/utils.d.ts.map +1 -0
  96. package/dist/src/editor/index.d.ts +12 -0
  97. package/dist/src/editor/index.d.ts.map +1 -0
  98. package/dist/src/editor/lib/cn.d.ts +3 -0
  99. package/dist/src/editor/lib/cn.d.ts.map +1 -0
  100. package/dist/src/editor/manifest.d.ts +19 -0
  101. package/dist/src/editor/manifest.d.ts.map +1 -0
  102. package/dist/src/editor/markdown-api.d.ts +36 -0
  103. package/dist/src/editor/markdown-api.d.ts.map +1 -0
  104. package/dist/src/editor/signals.d.ts +242 -0
  105. package/dist/src/editor/signals.d.ts.map +1 -0
  106. package/dist/src/editor/storage.d.ts +27 -0
  107. package/dist/src/editor/storage.d.ts.map +1 -0
  108. package/dist/src/editor/text-styling.d.ts +350 -0
  109. package/dist/src/editor/text-styling.d.ts.map +1 -0
  110. package/dist/src/editor/themes.d.ts +38 -0
  111. package/dist/src/editor/themes.d.ts.map +1 -0
  112. package/dist/src/editor/types.d.ts +454 -0
  113. package/dist/src/editor/types.d.ts.map +1 -0
  114. package/dist/src/error-collector.d.ts +56 -0
  115. package/dist/src/error-collector.d.ts.map +1 -0
  116. package/dist/src/handlers/component-ops.d.ts +34 -0
  117. package/dist/src/handlers/component-ops.d.ts.map +1 -0
  118. package/dist/src/handlers/markdown-ops.d.ts +41 -0
  119. package/dist/src/handlers/markdown-ops.d.ts.map +1 -0
  120. package/dist/src/handlers/request-utils.d.ts +20 -0
  121. package/dist/src/handlers/request-utils.d.ts.map +1 -0
  122. package/dist/src/handlers/source-writer.d.ts +51 -0
  123. package/dist/src/handlers/source-writer.d.ts.map +1 -0
  124. package/dist/src/html-processor.d.ts +63 -0
  125. package/dist/src/html-processor.d.ts.map +1 -0
  126. package/dist/src/index.d.ts +41 -0
  127. package/dist/src/index.d.ts.map +1 -0
  128. package/dist/src/manifest-writer.d.ts +111 -0
  129. package/dist/src/manifest-writer.d.ts.map +1 -0
  130. package/dist/src/media/contember.d.ts +15 -0
  131. package/dist/src/media/contember.d.ts.map +1 -0
  132. package/dist/src/media/local.d.ts +9 -0
  133. package/dist/src/media/local.d.ts.map +1 -0
  134. package/dist/src/media/s3.d.ts +12 -0
  135. package/dist/src/media/s3.d.ts.map +1 -0
  136. package/dist/src/media/types.d.ts +40 -0
  137. package/dist/src/media/types.d.ts.map +1 -0
  138. package/dist/src/preview-generator.d.ts +19 -0
  139. package/dist/src/preview-generator.d.ts.map +1 -0
  140. package/dist/src/seo-processor.d.ts +23 -0
  141. package/dist/src/seo-processor.d.ts.map +1 -0
  142. package/dist/src/source-finder/ast-extractors.d.ts +35 -0
  143. package/dist/src/source-finder/ast-extractors.d.ts.map +1 -0
  144. package/dist/src/source-finder/ast-parser.d.ts +16 -0
  145. package/dist/src/source-finder/ast-parser.d.ts.map +1 -0
  146. package/dist/src/source-finder/cache.d.ts +18 -0
  147. package/dist/src/source-finder/cache.d.ts.map +1 -0
  148. package/dist/src/source-finder/collection-finder.d.ts +29 -0
  149. package/dist/src/source-finder/collection-finder.d.ts.map +1 -0
  150. package/dist/src/source-finder/cross-file-tracker.d.ts +39 -0
  151. package/dist/src/source-finder/cross-file-tracker.d.ts.map +1 -0
  152. package/dist/src/source-finder/element-finder.d.ts +42 -0
  153. package/dist/src/source-finder/element-finder.d.ts.map +1 -0
  154. package/dist/src/source-finder/image-finder.d.ts +24 -0
  155. package/dist/src/source-finder/image-finder.d.ts.map +1 -0
  156. package/dist/src/source-finder/index.d.ts +9 -0
  157. package/dist/src/source-finder/index.d.ts.map +1 -0
  158. package/dist/src/source-finder/search-index.d.ts +27 -0
  159. package/dist/src/source-finder/search-index.d.ts.map +1 -0
  160. package/dist/src/source-finder/snippet-utils.d.ts +90 -0
  161. package/dist/src/source-finder/snippet-utils.d.ts.map +1 -0
  162. package/dist/src/source-finder/source-lookup.d.ts +16 -0
  163. package/dist/src/source-finder/source-lookup.d.ts.map +1 -0
  164. package/dist/src/source-finder/types.d.ts +167 -0
  165. package/dist/src/source-finder/types.d.ts.map +1 -0
  166. package/dist/src/source-finder/variable-extraction.d.ts +37 -0
  167. package/dist/src/source-finder/variable-extraction.d.ts.map +1 -0
  168. package/dist/src/tailwind-colors.d.ts +54 -0
  169. package/dist/src/tailwind-colors.d.ts.map +1 -0
  170. package/dist/src/tsconfig.tsbuildinfo +1 -0
  171. package/dist/src/types.d.ts +367 -0
  172. package/dist/src/types.d.ts.map +1 -0
  173. package/dist/src/utils.d.ts +61 -0
  174. package/dist/src/utils.d.ts.map +1 -0
  175. package/dist/src/vite-plugin.d.ts +14 -0
  176. package/dist/src/vite-plugin.d.ts.map +1 -0
  177. package/dist/types/tsconfig.tsbuildinfo +1 -0
  178. package/package.json +80 -0
  179. package/src/build-processor.ts +784 -0
  180. package/src/collection-scanner.ts +304 -0
  181. package/src/component-registry.ts +393 -0
  182. package/src/config.ts +74 -0
  183. package/src/dev-middleware.ts +525 -0
  184. package/src/dist/src/tsconfig.tsbuildinfo +1 -0
  185. package/src/editor/ai.ts +185 -0
  186. package/src/editor/api.ts +513 -0
  187. package/src/editor/color-utils.ts +556 -0
  188. package/src/editor/components/ai-chat.tsx +632 -0
  189. package/src/editor/components/ai-tooltip.tsx +179 -0
  190. package/src/editor/components/attribute-editor.tsx +596 -0
  191. package/src/editor/components/block-editor.tsx +546 -0
  192. package/src/editor/components/collections-browser.tsx +248 -0
  193. package/src/editor/components/color-toolbar.tsx +314 -0
  194. package/src/editor/components/confirm-dialog.tsx +69 -0
  195. package/src/editor/components/create-page-modal.tsx +163 -0
  196. package/src/editor/components/editable-highlights.tsx +260 -0
  197. package/src/editor/components/error-boundary.tsx +87 -0
  198. package/src/editor/components/fields.tsx +387 -0
  199. package/src/editor/components/frontmatter-fields.tsx +469 -0
  200. package/src/editor/components/highlight-overlay.ts +229 -0
  201. package/src/editor/components/image-overlay.tsx +230 -0
  202. package/src/editor/components/markdown-editor-overlay.tsx +505 -0
  203. package/src/editor/components/markdown-inline-editor.tsx +780 -0
  204. package/src/editor/components/media-library.tsx +297 -0
  205. package/src/editor/components/outline.tsx +402 -0
  206. package/src/editor/components/redirect-countdown.tsx +45 -0
  207. package/src/editor/components/seo-editor.tsx +498 -0
  208. package/src/editor/components/text-style-toolbar.tsx +362 -0
  209. package/src/editor/components/toast/toast-container.tsx +15 -0
  210. package/src/editor/components/toast/toast.tsx +49 -0
  211. package/src/editor/components/toast/types.ts +7 -0
  212. package/src/editor/components/toolbar.tsx +366 -0
  213. package/src/editor/config.ts +12 -0
  214. package/src/editor/constants.ts +106 -0
  215. package/src/editor/context.tsx +38 -0
  216. package/src/editor/dom.ts +357 -0
  217. package/src/editor/editor.ts +1510 -0
  218. package/src/editor/env.d.ts +4 -0
  219. package/src/editor/history.ts +355 -0
  220. package/src/editor/hooks/index.ts +19 -0
  221. package/src/editor/hooks/useAIHandlers.ts +345 -0
  222. package/src/editor/hooks/useBlockEditorHandlers.ts +206 -0
  223. package/src/editor/hooks/useElementDetection.ts +284 -0
  224. package/src/editor/hooks/useImageHoverDetection.ts +102 -0
  225. package/src/editor/hooks/useTextSelection.ts +187 -0
  226. package/src/editor/hooks/useTooltipState.ts +126 -0
  227. package/src/editor/hooks/utils.ts +101 -0
  228. package/src/editor/index.tsx +481 -0
  229. package/src/editor/lib/cn.ts +4 -0
  230. package/src/editor/manifest.ts +25 -0
  231. package/src/editor/markdown-api.ts +209 -0
  232. package/src/editor/signals.ts +1351 -0
  233. package/src/editor/storage.ts +266 -0
  234. package/src/editor/styles.css +465 -0
  235. package/src/editor/text-styling.ts +773 -0
  236. package/src/editor/themes.ts +210 -0
  237. package/src/editor/types.ts +591 -0
  238. package/src/error-collector.ts +106 -0
  239. package/src/handlers/component-ops.ts +463 -0
  240. package/src/handlers/markdown-ops.ts +202 -0
  241. package/src/handlers/request-utils.ts +151 -0
  242. package/src/handlers/source-writer.ts +649 -0
  243. package/src/html-processor.ts +1108 -0
  244. package/src/index.ts +284 -0
  245. package/src/manifest-writer.ts +371 -0
  246. package/src/media/contember.ts +84 -0
  247. package/src/media/local.ts +114 -0
  248. package/src/media/s3.ts +133 -0
  249. package/src/media/types.ts +33 -0
  250. package/src/preview-generator.ts +293 -0
  251. package/src/seo-processor.ts +567 -0
  252. package/src/source-finder/ast-extractors.ts +185 -0
  253. package/src/source-finder/ast-parser.ts +150 -0
  254. package/src/source-finder/cache.ts +76 -0
  255. package/src/source-finder/collection-finder.ts +335 -0
  256. package/src/source-finder/cross-file-tracker.ts +741 -0
  257. package/src/source-finder/element-finder.ts +387 -0
  258. package/src/source-finder/image-finder.ts +283 -0
  259. package/src/source-finder/index.ts +37 -0
  260. package/src/source-finder/search-index.ts +525 -0
  261. package/src/source-finder/snippet-utils.ts +668 -0
  262. package/src/source-finder/source-lookup.ts +200 -0
  263. package/src/source-finder/types.ts +210 -0
  264. package/src/source-finder/variable-extraction.ts +406 -0
  265. package/src/tailwind-colors.ts +874 -0
  266. package/src/tsconfig.json +25 -0
  267. package/src/types.ts +406 -0
  268. package/src/utils.ts +186 -0
  269. package/src/vite-plugin.ts +42 -0
@@ -0,0 +1,4 @@
1
+ declare module '*.css?inline' {
2
+ const css: string
3
+ export default css
4
+ }
@@ -0,0 +1,355 @@
1
+ import { computed, signal } from '@preact/signals'
2
+ import * as signals from './signals'
3
+ import {
4
+ saveAttributeEditsToStorage,
5
+ saveColorEditsToStorage,
6
+ saveEditsToStorage,
7
+ saveImageEditsToStorage,
8
+ } from './storage'
9
+ import type { Attribute, UndoAction, UndoTextAction } from './types'
10
+
11
+ // ============================================================================
12
+ // Undo/Redo Stack
13
+ // ============================================================================
14
+
15
+ const MAX_HISTORY = 100
16
+ const TEXT_DEBOUNCE_MS = 500
17
+
18
+ export const undoStack = signal<UndoAction[]>([])
19
+ export const redoStack = signal<UndoAction[]>([])
20
+
21
+ export const canUndo = computed(() => undoStack.value.length > 0 || pendingTextAction !== null)
22
+ export const canRedo = computed(() => redoStack.value.length > 0)
23
+
24
+ /** Guard flag to prevent re-recording during undo/redo application */
25
+ export let isApplyingUndoRedo = false
26
+
27
+ // ============================================================================
28
+ // Debounced Text Recording
29
+ // ============================================================================
30
+
31
+ let pendingTextAction: UndoTextAction | null = null
32
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null
33
+
34
+ function flushPendingText(): void {
35
+ if (pendingTextAction) {
36
+ pushUndo(pendingTextAction)
37
+ pendingTextAction = null
38
+ }
39
+ if (debounceTimer) {
40
+ clearTimeout(debounceTimer)
41
+ debounceTimer = null
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Record a text change with debouncing.
47
+ * Rapid keystrokes to the same element are grouped into a single undo unit.
48
+ */
49
+ export function recordTextChange(snapshot: UndoTextAction): void {
50
+ if (isApplyingUndoRedo) return
51
+
52
+ // If there's a pending action for the same element, extend it (keep original previous, update current)
53
+ if (pendingTextAction && pendingTextAction.cmsId === snapshot.cmsId) {
54
+ pendingTextAction = {
55
+ ...pendingTextAction,
56
+ currentHTML: snapshot.currentHTML,
57
+ currentText: snapshot.currentText,
58
+ }
59
+ } else {
60
+ // Flush any existing pending action for a different element
61
+ flushPendingText()
62
+ pendingTextAction = snapshot
63
+ }
64
+
65
+ // Clear redo stack on new user action
66
+ redoStack.value = []
67
+
68
+ // Reset debounce timer
69
+ if (debounceTimer) {
70
+ clearTimeout(debounceTimer)
71
+ }
72
+ debounceTimer = setTimeout(flushPendingText, TEXT_DEBOUNCE_MS)
73
+ }
74
+
75
+ // ============================================================================
76
+ // Immediate Recording (non-text changes)
77
+ // ============================================================================
78
+
79
+ /**
80
+ * Record a non-text change immediately (color, image, attribute, seo).
81
+ */
82
+ export function recordChange(action: UndoAction): void {
83
+ if (isApplyingUndoRedo) return
84
+
85
+ // Flush any pending text action first
86
+ flushPendingText()
87
+
88
+ pushUndo(action)
89
+
90
+ // Clear redo stack on new user action
91
+ redoStack.value = []
92
+ }
93
+
94
+ // ============================================================================
95
+ // Stack Operations
96
+ // ============================================================================
97
+
98
+ function pushUndo(action: UndoAction): void {
99
+ const stack = [...undoStack.value, action]
100
+ if (stack.length > MAX_HISTORY) {
101
+ stack.shift()
102
+ }
103
+ undoStack.value = stack
104
+ }
105
+
106
+ function pushRedo(action: UndoAction): void {
107
+ const stack = [...redoStack.value, action]
108
+ if (stack.length > MAX_HISTORY) {
109
+ stack.shift()
110
+ }
111
+ redoStack.value = stack
112
+ }
113
+
114
+ // ============================================================================
115
+ // Perform Undo / Redo
116
+ // ============================================================================
117
+
118
+ export function performUndo(): void {
119
+ // Flush pending text so it can be undone
120
+ flushPendingText()
121
+
122
+ const stack = undoStack.value
123
+ if (stack.length === 0) return
124
+
125
+ const action = stack[stack.length - 1]!
126
+ undoStack.value = stack.slice(0, -1)
127
+
128
+ isApplyingUndoRedo = true
129
+ try {
130
+ applyReverse(action)
131
+ pushRedo(action)
132
+ } finally {
133
+ isApplyingUndoRedo = false
134
+ }
135
+ }
136
+
137
+ export function performRedo(): void {
138
+ const stack = redoStack.value
139
+ if (stack.length === 0) return
140
+
141
+ const action = stack[stack.length - 1]!
142
+ redoStack.value = stack.slice(0, -1)
143
+
144
+ isApplyingUndoRedo = true
145
+ try {
146
+ applyForward(action)
147
+ pushUndo(action)
148
+ } finally {
149
+ isApplyingUndoRedo = false
150
+ }
151
+ }
152
+
153
+ // ============================================================================
154
+ // Apply Logic (Reverse = Undo, Forward = Redo)
155
+ // ============================================================================
156
+
157
+ function scrollToElement(element: HTMLElement): void {
158
+ if (!element.isConnected) return
159
+
160
+ const rect = element.getBoundingClientRect()
161
+ const inView = rect.top >= 0 && rect.bottom <= window.innerHeight
162
+ if (!inView) {
163
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' })
164
+ }
165
+ }
166
+
167
+ function applyReverse(action: UndoAction): void {
168
+ switch (action.type) {
169
+ case 'text':
170
+ scrollToElement(action.element)
171
+ applyTextState(action.cmsId, action.element, action.previousHTML, action.previousText, action.wasDirty)
172
+ break
173
+ case 'image':
174
+ scrollToElement(action.element)
175
+ applyImageState(action.cmsId, action.element, action.previousSrc, action.previousAlt, action.wasDirty)
176
+ break
177
+ case 'color':
178
+ scrollToElement(action.element)
179
+ applyColorState(action.cmsId, action.element, action.previousClassName, action.previousStyleCssText, action.previousClasses, action.wasDirty)
180
+ break
181
+ case 'attribute':
182
+ scrollToElement(action.element)
183
+ applyAttributeState(action.cmsId, action.element, action.previousAttributes, action.wasDirty)
184
+ break
185
+ case 'seo':
186
+ applySeoState(action.cmsId, action.previousValue, action.originalValue, action.wasDirty)
187
+ break
188
+ }
189
+ }
190
+
191
+ function applyForward(action: UndoAction): void {
192
+ switch (action.type) {
193
+ case 'text':
194
+ scrollToElement(action.element)
195
+ applyTextState(action.cmsId, action.element, action.currentHTML, action.currentText, true)
196
+ break
197
+ case 'image':
198
+ scrollToElement(action.element)
199
+ applyImageState(action.cmsId, action.element, action.currentSrc, action.currentAlt, true)
200
+ break
201
+ case 'color':
202
+ scrollToElement(action.element)
203
+ applyColorState(action.cmsId, action.element, action.currentClassName, action.currentStyleCssText, action.currentClasses, true)
204
+ break
205
+ case 'attribute':
206
+ scrollToElement(action.element)
207
+ applyAttributeState(action.cmsId, action.element, action.currentAttributes, true)
208
+ break
209
+ case 'seo':
210
+ applySeoState(action.cmsId, action.currentValue, action.originalValue, true)
211
+ break
212
+ }
213
+ }
214
+
215
+ // ============================================================================
216
+ // State Application Helpers
217
+ // ============================================================================
218
+
219
+ function applyTextState(
220
+ cmsId: string,
221
+ element: HTMLElement,
222
+ html: string,
223
+ text: string,
224
+ isDirty: boolean,
225
+ ): void {
226
+ if (element.isConnected) {
227
+ element.innerHTML = html
228
+ }
229
+
230
+ signals.updatePendingChange(cmsId, (c) => ({
231
+ ...c,
232
+ newText: text,
233
+ currentHTML: html,
234
+ isDirty,
235
+ }))
236
+
237
+ saveEditsToStorage(signals.pendingChanges.value)
238
+ }
239
+
240
+ function applyImageState(
241
+ cmsId: string,
242
+ element: HTMLImageElement,
243
+ src: string,
244
+ alt: string,
245
+ isDirty: boolean,
246
+ ): void {
247
+ if (element.isConnected) {
248
+ element.src = src
249
+ element.alt = alt
250
+ // When dirty (new image), clear srcset so browser uses src
251
+ // When restoring to original, restore the original srcset
252
+ const change = signals.getPendingImageChange(cmsId)
253
+ if (isDirty) {
254
+ element.removeAttribute('srcset')
255
+ } else if (change?.originalSrcSet) {
256
+ element.setAttribute('srcset', change.originalSrcSet)
257
+ }
258
+ }
259
+
260
+ signals.updatePendingImageChange(cmsId, (c) => ({
261
+ ...c,
262
+ newSrc: src,
263
+ newAlt: alt,
264
+ isDirty,
265
+ }))
266
+
267
+ saveImageEditsToStorage(signals.pendingImageChanges.value)
268
+ }
269
+
270
+ function applyColorState(
271
+ cmsId: string,
272
+ element: HTMLElement,
273
+ className: string,
274
+ styleCssText: string,
275
+ classes: Record<string, Attribute>,
276
+ isDirty: boolean,
277
+ ): void {
278
+ if (element.isConnected) {
279
+ element.className = className
280
+ element.style.cssText = styleCssText
281
+ }
282
+
283
+ signals.updatePendingColorChange(cmsId, (c) => ({
284
+ ...c,
285
+ newClasses: deepCopyAttributes(classes),
286
+ isDirty,
287
+ }))
288
+
289
+ saveColorEditsToStorage(signals.pendingColorChanges.value)
290
+ }
291
+
292
+ function applyAttributeState(
293
+ cmsId: string,
294
+ element: HTMLElement,
295
+ attributes: Record<string, Attribute>,
296
+ isDirty: boolean,
297
+ ): void {
298
+ if (element.isConnected) {
299
+ for (const [attrName, attr] of Object.entries(attributes)) {
300
+ if (attr.value === undefined || attr.value === '') {
301
+ element.removeAttribute(attrName)
302
+ } else {
303
+ element.setAttribute(attrName, attr.value)
304
+ }
305
+ }
306
+ }
307
+
308
+ signals.updatePendingAttributeChange(cmsId, (c) => ({
309
+ ...c,
310
+ newAttributes: deepCopyAttributes(attributes),
311
+ isDirty,
312
+ }))
313
+
314
+ saveAttributeEditsToStorage(signals.pendingAttributeChanges.value)
315
+ }
316
+
317
+ function applySeoState(
318
+ cmsId: string,
319
+ value: string,
320
+ originalValue: string,
321
+ isDirty: boolean,
322
+ ): void {
323
+ signals.setPendingSeoChange(cmsId, {
324
+ id: cmsId,
325
+ originalValue,
326
+ newValue: value,
327
+ isDirty,
328
+ })
329
+ }
330
+
331
+ // ============================================================================
332
+ // Clear History
333
+ // ============================================================================
334
+
335
+ export function clearHistory(): void {
336
+ undoStack.value = []
337
+ redoStack.value = []
338
+ pendingTextAction = null
339
+ if (debounceTimer) {
340
+ clearTimeout(debounceTimer)
341
+ debounceTimer = null
342
+ }
343
+ }
344
+
345
+ // ============================================================================
346
+ // Helpers
347
+ // ============================================================================
348
+
349
+ function deepCopyAttributes(attrs: Record<string, Attribute>): Record<string, Attribute> {
350
+ const copy: Record<string, Attribute> = {}
351
+ for (const [key, attr] of Object.entries(attrs)) {
352
+ copy[key] = { ...attr }
353
+ }
354
+ return copy
355
+ }
@@ -0,0 +1,19 @@
1
+ export { isEventOnCmsUI, useComponentClickHandler, useElementDetection } from './useElementDetection'
2
+ export type { ComponentClickHandlerOptions, OutlineState } from './useElementDetection'
3
+
4
+ export { isElementInCmsUI, usePositionTracking } from './utils'
5
+
6
+ export { useTooltipState } from './useTooltipState'
7
+ export type { TooltipState, UseTooltipStateOptions } from './useTooltipState'
8
+
9
+ export { useAIHandlers } from './useAIHandlers'
10
+ export type { AIHandlersOptions } from './useAIHandlers'
11
+
12
+ export { useBlockEditorHandlers } from './useBlockEditorHandlers'
13
+ export type { BlockEditorHandlersOptions } from './useBlockEditorHandlers'
14
+
15
+ export { clearTextSelection, restoreSelection, saveSelection, useTextSelection } from './useTextSelection'
16
+ export type { TextSelectionState } from './useTextSelection'
17
+
18
+ export { useImageHoverDetection } from './useImageHoverDetection'
19
+ export type { ImageHoverState } from './useImageHoverDetection'