hyperclayjs 1.0.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +360 -0
  3. package/README.template.md +276 -0
  4. package/communication/behaviorCollector.js +230 -0
  5. package/communication/sendMessage.js +48 -0
  6. package/communication/uploadFile.js +348 -0
  7. package/core/adminContenteditable.js +36 -0
  8. package/core/adminInputs.js +58 -0
  9. package/core/adminOnClick.js +31 -0
  10. package/core/adminResources.js +33 -0
  11. package/core/adminSystem.js +15 -0
  12. package/core/editmode.js +8 -0
  13. package/core/editmodeSystem.js +18 -0
  14. package/core/enablePersistentFormInputValues.js +62 -0
  15. package/core/isAdminOfCurrentResource.js +13 -0
  16. package/core/optionVisibilityRuleGenerator.js +160 -0
  17. package/core/savePage.js +196 -0
  18. package/core/savePageCore.js +236 -0
  19. package/core/setPageTypeOnDocumentElement.js +23 -0
  20. package/custom-attributes/ajaxElements.js +94 -0
  21. package/custom-attributes/autosize.js +17 -0
  22. package/custom-attributes/domHelpers.js +175 -0
  23. package/custom-attributes/events.js +15 -0
  24. package/custom-attributes/inputHelpers.js +11 -0
  25. package/custom-attributes/onclickaway.js +27 -0
  26. package/custom-attributes/onclone.js +35 -0
  27. package/custom-attributes/onpagemutation.js +20 -0
  28. package/custom-attributes/onrender.js +30 -0
  29. package/custom-attributes/preventEnter.js +13 -0
  30. package/custom-attributes/sortable.js +76 -0
  31. package/dom-utilities/All.js +412 -0
  32. package/dom-utilities/getDataFromForm.js +60 -0
  33. package/dom-utilities/insertStyleTag.js +28 -0
  34. package/dom-utilities/onDomReady.js +7 -0
  35. package/dom-utilities/onLoad.js +7 -0
  36. package/hyperclay.js +465 -0
  37. package/module-dependency-graph.json +612 -0
  38. package/package.json +95 -0
  39. package/string-utilities/copy-to-clipboard.js +35 -0
  40. package/string-utilities/emmet-html.js +54 -0
  41. package/string-utilities/query.js +1 -0
  42. package/string-utilities/slugify.js +21 -0
  43. package/ui/info.js +39 -0
  44. package/ui/prompts.js +179 -0
  45. package/ui/theModal.js +677 -0
  46. package/ui/toast.js +273 -0
  47. package/utilities/cookie.js +45 -0
  48. package/utilities/debounce.js +12 -0
  49. package/utilities/mutation.js +403 -0
  50. package/utilities/nearest.js +97 -0
  51. package/utilities/pipe.js +1 -0
  52. package/utilities/throttle.js +21 -0
  53. package/vendor/Sortable.js +3351 -0
  54. package/vendor/idiomorph.min.js +8 -0
  55. package/vendor/tailwind-base.css +1471 -0
  56. package/vendor/tailwind-play.js +169 -0
@@ -0,0 +1,403 @@
1
+ /**
2
+ *
3
+ * Lightweight wrapper around MutationObserver that provides methods to watch DOM changes.
4
+ * Uses a single observer instance internally to improve performance.
5
+ *
6
+ * // Watch for any DOM changes (additions, removals, attribute changes)
7
+ * Mutation.onAnyChange({ debounce: 200 }, changes => {
8
+ * changes.forEach(change => {
9
+ * if (change.type === 'add') {
10
+ * console.log('Added:', change.element);
11
+ * console.log('To parent:', change.parent);
12
+ * console.log('Between:', change.previousSibling, change.nextSibling);
13
+ * }
14
+ * if (change.type === 'remove') {
15
+ * console.log('Removed:', change.element);
16
+ * console.log('From parent:', change.parent);
17
+ * }
18
+ * if (change.type === 'attribute') {
19
+ * console.log('Changed:', change.attribute);
20
+ * console.log('From:', change.oldValue, 'to:', change.newValue);
21
+ * }
22
+ * });
23
+ * });
24
+ *
25
+ * // Watch for element additions or removals
26
+ * Mutation.onAddOrRemove({ debounce: 200 }, changes => {
27
+ * changes.forEach(change => {
28
+ * const action = change.type === 'add' ? 'added to' : 'removed from';
29
+ * console.log(`${change.element.tagName} ${action} ${change.parent.tagName}`);
30
+ * });
31
+ * });
32
+ *
33
+ * // Watch for element additions
34
+ * Mutation.onAddElement({ debounce: 200 }, changes => {
35
+ * changes.forEach(({ element, parent }) => {
36
+ * console.log(`${element.tagName} added to ${parent.tagName}`);
37
+ * });
38
+ * });
39
+ *
40
+ * // Watch for element removals with location info
41
+ * Mutation.onRemoveElement({ debounce: 200 }, changes => {
42
+ * changes.forEach(({ element, parent, previousSibling, nextSibling }) => {
43
+ * console.log(`${element.tagName} removed from ${parent.tagName}`);
44
+ * console.log('Was between:', previousSibling?.tagName, nextSibling?.tagName);
45
+ * });
46
+ * });
47
+ *
48
+ * // Watch for attribute changes
49
+ * Mutation.onAttribute({ debounce: 200 }, changes => {
50
+ * // Debounce collects multiple changes into an array
51
+ * changes.forEach(({ element, attribute, oldValue, newValue: newValue }) => {
52
+ * console.log(`${element.tagName} ${attribute} changed from ${oldValue} to ${newValue}`);
53
+ * });
54
+ * });
55
+ *
56
+ */
57
+
58
+ const dummyElem = document.createElement("div");
59
+
60
+ const Mutation = {
61
+ _callbacks: {
62
+ anyChange: [],
63
+ addOrRemove: [],
64
+ addElement: [],
65
+ removeElement: [],
66
+ attribute: []
67
+ },
68
+
69
+ _observing: false,
70
+ debug: false,
71
+
72
+ _log(message, data = null, type = 'log') {
73
+ if (!this.debug) return;
74
+
75
+ const timestamp = new Date().toISOString();
76
+ const prefix = `[Mutation ${timestamp}]`;
77
+
78
+ if (data) {
79
+ console[type](prefix, message, data);
80
+ } else {
81
+ console[type](prefix, message);
82
+ }
83
+ },
84
+
85
+ _notify(type, changes) {
86
+ this._log(`Notifying ${this._callbacks[type].length} callbacks of type "${type}"`, { changes });
87
+
88
+ for (const callback of this._callbacks[type]) {
89
+ const { fn, debounce = 0, selectorFilter, omitChangeDetails } = callback;
90
+
91
+ // Apply filtering if there's a selector filter
92
+ let filteredChanges = changes;
93
+ if (selectorFilter) {
94
+ this._log('Applying selector filter', { selectorFilter });
95
+ filteredChanges = changes.filter(change => {
96
+ if (typeof selectorFilter === 'string') {
97
+ const matches = change.element.matches?.(selectorFilter) || false;
98
+ this._log(`Selector "${selectorFilter}" match:`, { element: change.element, matches });
99
+ return matches;
100
+ }
101
+ if (typeof selectorFilter === 'function') {
102
+ const matches = selectorFilter(change.element);
103
+ this._log('Custom filter match:', { element: change.element, matches });
104
+ return matches;
105
+ }
106
+ return false;
107
+ });
108
+ this._log('Changes after filtering:', { filteredChanges });
109
+ }
110
+
111
+ // Skip if we have a selector filter and no matching changes
112
+ if (selectorFilter && !filteredChanges.length) {
113
+ this._log('No changes passed the filter, skipping callback');
114
+ continue;
115
+ }
116
+
117
+ // Handle debouncing and callback execution
118
+ if (debounce === 0) {
119
+ // No debounce, execute immediately
120
+ try {
121
+ if (omitChangeDetails) {
122
+ fn();
123
+ } else {
124
+ fn(filteredChanges);
125
+ }
126
+ } catch (e) {
127
+ this._log('Error in callback execution:', e, 'error');
128
+ console.error('Error in Mutation callback:', e);
129
+ }
130
+ } else {
131
+ // Clear any existing timeout
132
+ if (callback.timeout) {
133
+ clearTimeout(callback.timeout);
134
+ callback.timeout = null;
135
+ }
136
+
137
+ if (omitChangeDetails) {
138
+ // For omitChangeDetails, just reset the timer
139
+ callback.timeout = setTimeout(() => {
140
+ callback.timeout = null;
141
+ try {
142
+ this._log('Executing debounced callback (no details)');
143
+ fn();
144
+ } catch (e) {
145
+ this._log('Error in callback execution:', e, 'error');
146
+ console.error('Error in Mutation callback:', e);
147
+ }
148
+ }, debounce);
149
+ } else {
150
+ // For callbacks with change details, accumulate changes
151
+ if (!callback.pendingChanges) {
152
+ callback.pendingChanges = [];
153
+ }
154
+ callback.pendingChanges.push(...filteredChanges);
155
+
156
+ // Reset the timer
157
+ callback.timeout = setTimeout(() => {
158
+ const changes = callback.pendingChanges;
159
+ callback.pendingChanges = null; // Reset to null, not empty array
160
+ callback.timeout = null; // Clear the timeout reference
161
+ try {
162
+ this._log('Executing debounced callback with changes:', { changes });
163
+ if (changes && changes.length > 0) {
164
+ fn(changes);
165
+ }
166
+ } catch (e) {
167
+ this._log('Error in callback execution:', e, 'error');
168
+ console.error('Error in Mutation callback:', e);
169
+ }
170
+ }, debounce);
171
+ }
172
+ }
173
+ }
174
+ },
175
+
176
+ _shouldIgnore(element) {
177
+ while (element && element.nodeType === 1) {
178
+ if (element.hasAttribute?.('mutations-ignore') || element.hasAttribute?.('save-ignore')) {
179
+ return true;
180
+ }
181
+ element = element.parentElement;
182
+ }
183
+ return false;
184
+ },
185
+
186
+ _handleMutations(mutations) {
187
+ this._log(`Processing ${mutations.length} mutations`, { mutations });
188
+
189
+ const changes = [];
190
+ const changesByType = {
191
+ add: [],
192
+ remove: [],
193
+ attribute: [],
194
+ characterData: []
195
+ };
196
+
197
+ for (const mutation of mutations) {
198
+ // Check if the target or any parent has mutations-ignore attribute
199
+ if (this._shouldIgnore(mutation.target)) {
200
+ this._log('Ignoring mutation due to mutations-ignore attribute', { mutation });
201
+ continue;
202
+ }
203
+
204
+ if (mutation.type === 'characterData') {
205
+ this._log('Processing characterData mutation', {
206
+ element: mutation.target.parentElement,
207
+ oldValue: mutation.oldValue,
208
+ newValue: mutation.target.textContent
209
+ });
210
+
211
+ const change = {
212
+ type: 'characterData',
213
+ element: mutation.target.parentElement ?? dummyElem, // hacky, but ensures we always pass an element in the callback
214
+ oldValue: mutation.oldValue,
215
+ newValue: mutation.target.textContent
216
+ };
217
+ changes.push(change);
218
+ changesByType.characterData.push(change);
219
+ }
220
+
221
+ if (mutation.type === 'childList') {
222
+ this._log('Processing childList mutation', {
223
+ addedNodes: mutation.addedNodes,
224
+ removedNodes: mutation.removedNodes
225
+ });
226
+
227
+ for (const node of mutation.addedNodes) {
228
+ if (node.nodeType === 1 && !this._shouldIgnore(node)) {
229
+ const addedNodes = [node, ...node.querySelectorAll('*')];
230
+ this._log(`Processing ${addedNodes.length} added nodes`, { addedNodes });
231
+
232
+ for (const element of addedNodes) {
233
+ const change = {
234
+ type: 'add',
235
+ element,
236
+ parent: mutation.target,
237
+ previousSibling: mutation.previousSibling,
238
+ nextSibling: mutation.nextSibling
239
+ };
240
+ changes.push(change);
241
+ changesByType.add.push(change);
242
+ }
243
+ }
244
+ }
245
+
246
+ for (const node of mutation.removedNodes) {
247
+ if (node.nodeType === 1 && !node.hasAttribute?.('save-ignore') && !node.hasAttribute?.('mutations-ignore')) {
248
+ const removedNodes = [node, ...node.querySelectorAll('*')];
249
+ this._log(`Processing ${removedNodes.length} removed nodes`, { removedNodes });
250
+
251
+ for (const element of removedNodes) {
252
+ const change = {
253
+ type: 'remove',
254
+ element,
255
+ parent: mutation.target,
256
+ previousSibling: mutation.previousSibling,
257
+ nextSibling: mutation.nextSibling
258
+ };
259
+ changes.push(change);
260
+ changesByType.remove.push(change);
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ if (mutation.type === 'attributes') {
267
+ this._log('Processing attribute mutation', {
268
+ element: mutation.target,
269
+ attribute: mutation.attributeName,
270
+ oldValue: mutation.oldValue,
271
+ newValue: mutation.target.getAttribute(mutation.attributeName)
272
+ });
273
+
274
+ const change = {
275
+ type: 'attribute',
276
+ element: mutation.target,
277
+ attribute: mutation.attributeName,
278
+ oldValue: mutation.oldValue,
279
+ newValue: mutation.target.getAttribute(mutation.attributeName)
280
+ };
281
+ changes.push(change);
282
+ changesByType.attribute.push(change);
283
+ }
284
+ }
285
+
286
+ if (changes.length) {
287
+ this._log('Processing collected changes', {
288
+ total: changes.length,
289
+ byType: {
290
+ add: changesByType.add.length,
291
+ remove: changesByType.remove.length,
292
+ attribute: changesByType.attribute.length
293
+ }
294
+ });
295
+
296
+ this._notify('anyChange', changes);
297
+
298
+ const addOrRemove = [...changesByType.add, ...changesByType.remove];
299
+ if (addOrRemove.length) {
300
+ this._notify('addOrRemove', addOrRemove);
301
+ }
302
+
303
+ if (changesByType.add.length) {
304
+ this._notify('addElement', changesByType.add);
305
+ }
306
+ if (changesByType.remove.length) {
307
+ this._notify('removeElement', changesByType.remove);
308
+ }
309
+ if (changesByType.attribute.length) {
310
+ this._notify('attribute', changesByType.attribute);
311
+ }
312
+ } else {
313
+ this._log('No changes to process after filtering');
314
+ }
315
+ },
316
+
317
+ _observer: null,
318
+
319
+ _initializeObserver() {
320
+ if (!this._observer) {
321
+ this._log('Initializing MutationObserver');
322
+ this._observer = new MutationObserver(this._handleMutations.bind(this));
323
+ }
324
+ },
325
+
326
+ _addCallback(type, options = {}, callback) {
327
+ this._log('Adding callback', { type, options });
328
+
329
+ if (options.debug) {
330
+ this.debug = true;
331
+ }
332
+
333
+ const cb = {
334
+ fn: callback,
335
+ debounce: options.debounce || 0,
336
+ selectorFilter: options.selectorFilter,
337
+ omitChangeDetails: options.omitChangeDetails,
338
+ timeout: null,
339
+ pendingChanges: null
340
+ };
341
+
342
+ this._callbacks[type].push(cb);
343
+ this._log(`Added callback to ${type}. Total callbacks:`, {
344
+ [type]: this._callbacks[type].length
345
+ });
346
+
347
+ this._startObserving();
348
+
349
+ return () => {
350
+ this._log('Removing callback', { type });
351
+ const index = this._callbacks[type].indexOf(cb);
352
+ if (index !== -1) {
353
+ clearTimeout(cb.timeout);
354
+ cb.pendingChanges = null;
355
+ this._callbacks[type].splice(index, 1);
356
+ this._log(`Removed callback from ${type}. Remaining callbacks:`, {
357
+ [type]: this._callbacks[type].length
358
+ });
359
+ }
360
+ };
361
+ },
362
+
363
+ _startObserving() {
364
+ if (this._observing) {
365
+ this._log('Already observing, skipping initialization');
366
+ return;
367
+ }
368
+
369
+ this._log('Starting observation');
370
+ this._initializeObserver();
371
+ this._observer.observe(document.body, {
372
+ childList: true,
373
+ attributes: true,
374
+ subtree: true,
375
+ characterData: true,
376
+ attributeOldValue: true
377
+ });
378
+ this._observing = true;
379
+ this._log('Observation started');
380
+ },
381
+
382
+ onAnyChange(options = {}, callback) {
383
+ return this._addCallback('anyChange', options, callback);
384
+ },
385
+
386
+ onAddOrRemove(options = {}, callback) {
387
+ return this._addCallback('addOrRemove', options, callback);
388
+ },
389
+
390
+ onAddElement(options = {}, callback) {
391
+ return this._addCallback('addElement', options, callback);
392
+ },
393
+
394
+ onRemoveElement(options = {}, callback) {
395
+ return this._addCallback('removeElement', options, callback);
396
+ },
397
+
398
+ onAttribute(options = {}, callback) {
399
+ return this._addCallback('attribute', options, callback);
400
+ }
401
+ };
402
+
403
+ export default Mutation;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Searches for elements matching a CSS selector by exploring the DOM tree outward from a starting point,
3
+ * checking nearby elements first before moving to more distant parts of the document.
4
+ *
5
+ * Unlike `element.closest()` which only searches ancestors, this explores the DOM tree in a
6
+ * unique pattern designed to find nearby elements in the visual layout:
7
+ *
8
+ * TRAVERSAL ORDER (for each level):
9
+ * 1. Current element
10
+ * 2. All children of current element (deeply)
11
+ * 3. All previous siblings (right-to-left), exploring each fully with descendants
12
+ * 4. All next siblings (left-to-right), exploring each fully with descendants
13
+ * 5. Move to parent and repeat from step 1
14
+ *
15
+ * KEY FEATURES:
16
+ * - Global visited cache prevents revisiting nodes (big performance boost)
17
+ * - Searches "outward" from start position, checking nearby elements first
18
+ * - Explores each sibling's entire subtree before moving to next sibling
19
+ * - Continues up the ancestor chain until document.body
20
+ *
21
+ * USE CASES:
22
+ * - Finding related UI elements that might be siblings or cousins
23
+ * - Locating the "next" instance of something in reading order
24
+ * - Finding nearby form fields, buttons, or other interactive elements
25
+ *
26
+ * @param {Element} startElem - Starting element for the search
27
+ * @param {string} selector - CSS selector to match
28
+ * @param {Function} elementFoundReturnValue - Transform function for the found element
29
+ * @returns {*} Transformed element if found, null otherwise
30
+ */
31
+ export default function nearest (startElem, selector, elementFoundReturnValue = x => x) {
32
+ const visited = new Set();
33
+
34
+ // Check node and its descendants using BFS
35
+ function checkDeep(root) {
36
+ if (!root || visited.has(root)) return null;
37
+
38
+ const queue = [root];
39
+ const localVisited = new Set(); // Prevent cycles within this BFS
40
+
41
+ while (queue.length > 0) {
42
+ const node = queue.shift();
43
+ if (!node || localVisited.has(node) || visited.has(node)) continue;
44
+
45
+ visited.add(node);
46
+ localVisited.add(node);
47
+
48
+ if (node.matches(selector)) {
49
+ return elementFoundReturnValue(node);
50
+ }
51
+
52
+ queue.push(...node.children);
53
+ }
54
+ return null;
55
+ }
56
+
57
+ // Check siblings in a direction
58
+ function checkSiblings(start, direction) {
59
+ let sibling = start[direction];
60
+ while (sibling) {
61
+ const result = checkDeep(sibling);
62
+ if (result) return result;
63
+ sibling = sibling[direction];
64
+ }
65
+ return null;
66
+ }
67
+
68
+ // Main traversal
69
+ // check current → children → siblings → move up
70
+ let current = startElem;
71
+
72
+ while (current) {
73
+ // Check current node (shallow)
74
+ if (!visited.has(current)) {
75
+ visited.add(current);
76
+ if (current.matches(selector)) {
77
+ return elementFoundReturnValue(current);
78
+ }
79
+ }
80
+
81
+ // Check children deeply
82
+ for (const child of current.children) {
83
+ const result = checkDeep(child);
84
+ if (result) return result;
85
+ }
86
+
87
+ // Check siblings deeply
88
+ let result = checkSiblings(current, 'previousElementSibling') ||
89
+ checkSiblings(current, 'nextElementSibling');
90
+ if (result) return result;
91
+
92
+ // Move up to parent
93
+ current = current.parentElement;
94
+ }
95
+
96
+ return null;
97
+ }
@@ -0,0 +1 @@
1
+ export default (...fns) => x => fns.reduce((v, f) => f(v), x);
@@ -0,0 +1,21 @@
1
+ export default function throttle(callback, delay, executeFirst = true) {
2
+ let lastCall = executeFirst ? 0 : Date.now();
3
+ let timeoutId = null;
4
+
5
+ return function (...args) {
6
+ const now = Date.now();
7
+ const remaining = delay - (now - lastCall);
8
+
9
+ if (remaining <= 0) {
10
+ clearTimeout(timeoutId);
11
+ lastCall = now;
12
+ return callback.apply(this, args);
13
+ } else if (!timeoutId) {
14
+ timeoutId = setTimeout(() => {
15
+ lastCall = Date.now();
16
+ timeoutId = null;
17
+ callback.apply(this, args);
18
+ }, remaining);
19
+ }
20
+ };
21
+ }