hyperclayjs 1.3.1 → 1.4.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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -45
  3. package/communication/behaviorCollector.js +7 -4
  4. package/communication/sendMessage.js +7 -4
  5. package/communication/uploadFile.js +8 -5
  6. package/core/autosave.js +5 -47
  7. package/core/editmodeSystem.js +8 -5
  8. package/core/enablePersistentFormInputValues.js +7 -4
  9. package/core/exportToWindow.js +14 -0
  10. package/core/optionVisibilityRuleGenerator.js +8 -5
  11. package/core/savePage.js +47 -14
  12. package/core/savePageCore.js +10 -7
  13. package/custom-attributes/domHelpers.js +7 -4
  14. package/custom-attributes/sortable.js +23 -16
  15. package/dom-utilities/All.js +9 -6
  16. package/dom-utilities/getDataFromForm.js +8 -5
  17. package/dom-utilities/insertStyleTag.js +8 -5
  18. package/dom-utilities/onDomReady.js +7 -4
  19. package/dom-utilities/onLoad.js +7 -4
  20. package/hyperclay.js +90 -31
  21. package/module-dependency-graph.json +103 -135
  22. package/package.json +1 -1
  23. package/string-utilities/copy-to-clipboard.js +7 -4
  24. package/string-utilities/query.js +8 -5
  25. package/string-utilities/slugify.js +8 -5
  26. package/ui/prompts.js +49 -31
  27. package/ui/theModal.js +50 -6
  28. package/ui/toast-hyperclay.js +27 -11
  29. package/ui/toast.js +82 -92
  30. package/utilities/cookie.js +8 -5
  31. package/utilities/debounce.js +7 -4
  32. package/utilities/loadVendorScript.js +57 -0
  33. package/utilities/mutation.js +9 -6
  34. package/utilities/nearest.js +7 -4
  35. package/utilities/throttle.js +7 -4
  36. package/vendor/Sortable.vendor.js +2 -0
  37. package/vendor/idiomorph.min.js +8 -5
  38. package/vendor/tailwind-play.js +16 -162
  39. package/vendor/tailwind-play.vendor.js +169 -0
  40. package/string-utilities/emmet-html.js +0 -60
  41. package/ui/info.js +0 -47
  42. package/vendor/Sortable.js +0 -3351
package/LICENSE CHANGED
@@ -19,3 +19,24 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
23
+ ---
24
+
25
+ ## Third-Party Licenses
26
+
27
+ This project includes the following third-party libraries:
28
+
29
+ ### Idiomorph
30
+ - Copyright (c) Big Sky Software
31
+ - License: 0BSD (Zero-Clause BSD)
32
+ - https://github.com/bigskysoftware/idiomorph
33
+
34
+ ### Tailwind CSS
35
+ - Copyright (c) Tailwind Labs, Inc.
36
+ - License: MIT
37
+ - https://github.com/tailwindlabs/tailwindcss
38
+
39
+ ### Sortable.js
40
+ - Copyright (c) All contributors to Sortable
41
+ - License: MIT
42
+ - https://github.com/SortableJS/Sortable
package/README.md CHANGED
@@ -15,22 +15,27 @@ A modular JavaScript library for building interactive HTML applications with Hyp
15
15
 
16
16
  ### Using CDN (Self-detecting Loader)
17
17
 
18
- The self-detecting loader reads URL parameters and automatically loads the requested features with all dependencies:
18
+ The self-detecting loader reads URL parameters and automatically loads the requested features with all dependencies.
19
19
 
20
- ```html
21
- <!-- Minimal setup -->
22
- <script src="https://hyperclay.com/js/hyperclay.js?preset=minimal" type="module"></script>
20
+ Destructure directly from the import:
23
21
 
24
- <!-- Standard setup -->
25
- <script src="https://hyperclay.com/js/hyperclay.js?preset=standard" type="module"></script>
22
+ ```html
23
+ <script type="module">
24
+ const { toast, savePage } = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/hyperclay.js?preset=standard');
25
+ toast('Hello!');
26
+ </script>
27
+ ```
26
28
 
27
- <!-- Custom features -->
28
- <script src="https://hyperclay.com/js/hyperclay.js?features=save,admin,toast,ajax" type="module"></script>
29
+ Or with custom features:
29
30
 
30
- <!-- Everything -->
31
- <script src="https://hyperclay.com/js/hyperclay.js?preset=everything" type="module"></script>
31
+ ```html
32
+ <script type="module">
33
+ const { toast, ask } = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/hyperclay.js?features=toast,dialogs');
34
+ </script>
32
35
  ```
33
36
 
37
+ **Note:** Presets include `export-to-window` by default, which also exports to `window.hyperclay`. Omit it from custom features if you only want ES module exports.
38
+
34
39
  ### Using NPM
35
40
 
36
41
  ```bash
@@ -52,91 +57,110 @@ import 'hyperclayjs/presets/standard.js';
52
57
 
53
58
  | Module | Size | Description |
54
59
  |--------|------|-------------|
55
- | admin | 5.4KB | Hides admin inputs, scripts, contenteditable, onclick for regular viewers |
56
- | autosave | 2.4KB | Auto-save on DOM changes, unsaved changes warning |
57
- | edit-mode | 1.7KB | Toggle edit mode on/off |
60
+ | autosave | 1.2KB | Auto-save on DOM changes, unsaved changes warning |
61
+ | edit-mode | 1.8KB | Toggle edit mode on hyperclay on/off |
62
+ | edit-mode-helpers | 5.4KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
58
63
  | option-visibility | 4.7KB | Dynamic show/hide based on ancestor state with option:attribute="value" |
59
- | persist | 2.4KB | Persist form values to the DOM with [persist] attribute |
60
- | save-core | 5.8KB | Basic save function only - hyperclay.savePage() |
61
- | save-system | 4.1KB | Manual save: keyboard shortcut (CMD+S), save button, change tracking |
64
+ | persist | 2.5KB | Persist input/select/textarea values to the DOM with [persist] attribute |
65
+ | save-core | 5.9KB | Basic save function only - hyperclay.savePage() |
66
+ | save-system | 4.9KB | Manual save: keyboard shortcut (CMD+S), save button, change tracking |
62
67
 
63
68
  ### Custom Attributes (HTML enhancements)
64
69
 
65
70
  | Module | Size | Description |
66
71
  |--------|------|-------------|
67
72
  | ajax-elements | 2.8KB | [ajax-form], [ajax-button] for async form submissions |
68
- | dom-helpers | 5.6KB | el.nearest, el.val, el.text, el.exec, el.cycle |
73
+ | dom-helpers | 5.7KB | el.nearest, el.val, el.text, el.exec, el.cycle |
69
74
  | event-attrs | 3.6KB | [onclickaway], [onclone], [onpagemutation], [onrender] |
70
75
  | input-helpers | 1.2KB | [prevent-enter], [autosize] for textareas |
71
- | sortable | 118.1KB | Drag-drop sorting with [sortable] - includes Sortable.js vendor library |
76
+ | sortable | 2.8KB | Drag-drop sorting with [sortable], lazy-loads ~118KB Sortable.js in edit mode |
72
77
 
73
78
  ### UI Components (User interface elements)
74
79
 
75
80
  | Module | Size | Description |
76
81
  |--------|------|-------------|
77
- | dialogs | 11.2KB | ask(), consent(), tell(), info(), snippet() functions |
78
- | modal | 18.5KB | Full modal window creation system - window.theModal |
79
- | tailwind-play | 362.3KB | Live Tailwind CSS editing - no need for a build system |
80
- | toast | 7.3KB | Success/error message notifications - toast(msg, msgType) |
82
+ | dialogs | 8.4KB | ask(), consent(), tell(), snippet() dialog functions |
83
+ | tailwind-play | 0.7KB | Live Tailwind CSS editing, lazy-loads ~370KB script in edit mode only |
84
+ | the-modal | 19.8KB | Full modal window creation system - window.theModal |
85
+ | toast | 7.7KB | Success/error message notifications, toast(msg, msgType) |
81
86
 
82
87
  ### Utilities (Core utilities (often auto-included))
83
88
 
84
89
  | Module | Size | Description |
85
90
  |--------|------|-------------|
86
- | cookie | 1.3KB | Cookie management (often auto-included) |
91
+ | cookie | 1.4KB | Cookie management (often auto-included) |
87
92
  | debounce | 0.4KB | Function debouncing |
88
- | mutation | 12.9KB | DOM mutation observation (often auto-included) |
89
- | nearest | 3.3KB | Find nearest elements (often auto-included) |
90
- | throttle | 0.7KB | Function throttling |
93
+ | mutation | 13KB | DOM mutation observation (often auto-included) |
94
+ | nearest | 3.4KB | Find nearest elements (often auto-included) |
95
+ | throttle | 0.8KB | Function throttling |
91
96
 
92
97
  ### DOM Utilities (DOM manipulation helpers)
93
98
 
94
99
  | Module | Size | Description |
95
100
  |--------|------|-------------|
96
- | all-js | 13.9KB | Full DOM manipulation library |
97
- | dom-ready | 0.3KB | DOM ready callback |
98
- | form-data | 1.9KB | Extract form data as an object |
99
- | style-injection | 1KB | Dynamic stylesheet injection |
101
+ | all-js | 14KB | Full DOM manipulation library |
102
+ | dom-ready | 0.4KB | DOM ready callback |
103
+ | form-data | 2KB | Extract form data as an object |
104
+ | style-injection | 1.1KB | Dynamic stylesheet injection |
100
105
 
101
106
  ### String Utilities (String manipulation helpers)
102
107
 
103
108
  | Module | Size | Description |
104
109
  |--------|------|-------------|
105
- | clipboard | 0.8KB | Clipboard utility |
106
- | emmet | 1.5KB | Emmet-like HTML generation |
107
- | query-params | 0.2KB | Parse URL search params |
108
- | slugify | 0.6KB | URL-friendly slug generator |
110
+ | copy-to-clipboard | 0.9KB | Clipboard utility |
111
+ | query-params | 0.3KB | Parse URL search params |
112
+ | slugify | 0.7KB | URL-friendly slug generator |
109
113
 
110
114
  ### Communication & Files (File handling and messaging)
111
115
 
112
116
  | Module | Size | Description |
113
117
  |--------|------|-------------|
114
- | file-upload | 10.6KB | File upload with progress |
115
- | send-message | 1.3KB | Message sending utility |
118
+ | file-upload | 10.7KB | File upload with progress |
119
+ | send-message | 1.4KB | Message sending utility |
116
120
 
117
121
  ### Vendor Libraries (Third-party libraries)
118
122
 
119
123
  | Module | Size | Description |
120
124
  |--------|------|-------------|
121
- | idiomorph | 8.1KB | Efficient DOM morphing library |
125
+ | idiomorph | 8.2KB | Efficient DOM morphing library |
122
126
 
123
127
  ## Presets
124
128
 
125
- ### Minimal (~22.6KB)
129
+ ### Minimal (~24.3KB)
126
130
  Essential features for basic editing
127
131
 
128
- **Modules:** `save-core`, `save-system`, `admin`, `toast`
132
+ **Modules:** `save-core`, `save-system`, `edit-mode-helpers`, `toast`, `export-to-window`
129
133
 
130
- ### Standard (~38.9KB)
134
+ ### Standard (~40.8KB)
131
135
  Standard feature set for most use cases
132
136
 
133
- **Modules:** `save-core`, `save-system`, `admin`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`
137
+ **Modules:** `save-core`, `save-system`, `edit-mode-helpers`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`, `export-to-window`
134
138
 
135
- ### Everything (~629.5KB)
139
+ ### Everything (~145.1KB)
136
140
  All available features
137
141
 
138
142
  Includes all available modules across all categories.
139
143
 
144
+ ## Lazy-Loaded Modules
145
+
146
+ Some modules with large vendor dependencies are **lazy-loaded** to optimize page performance:
147
+
148
+ | Module | Wrapper Size | Vendor Size | Loaded When |
149
+ |--------|-------------|-------------|-------------|
150
+ | `sortable` | ~3KB | ~118KB | Edit mode only |
151
+ | `tailwind-play` | ~1KB | ~370KB | Edit mode only |
152
+
153
+ **How it works:**
154
+ - The wrapper module checks if the page is in edit mode (`isEditMode`)
155
+ - If true, it injects a `<script save-ignore>` tag that loads the vendor script
156
+ - If false, nothing is loaded - viewers don't download the heavy scripts
157
+ - The `save-ignore` attribute strips the script tag when the page is saved
158
+
159
+ This means:
160
+ - **Editors** get full functionality when needed
161
+ - **Viewers** never download ~500KB of vendor scripts
162
+ - **Saved pages** stay clean with no leftover script tags
163
+
140
164
  ## Visual Configurator
141
165
 
142
166
  Explore features and build your custom bundle with our interactive configurator:
@@ -217,7 +241,7 @@ The configurator dynamically loads this file to always show accurate information
217
241
  - Safari 15.4+
218
242
  - Edge 89+
219
243
 
220
- The loader uses top-level await, which means any `<script type="module">` placed after it will automatically wait for HyperclayJS to finish loading. No ready events or promises needed.
244
+ The loader uses ES modules with top-level await. Use `await import()` to ensure modules finish loading before your code runs.
221
245
 
222
246
  ## API Examples
223
247
 
@@ -278,7 +302,7 @@ tell("Welcome to Hyperclay!");
278
302
  Click outside this div
279
303
  </div>
280
304
 
281
- <!-- Persist form values -->
305
+ <!-- Persist input/select/textarea values -->
282
306
  <input type="text" name="username" persist>
283
307
  ```
284
308
 
@@ -320,7 +344,7 @@ myFeature();
320
344
  <script src="/js/hyperclay.js?preset=standard" type="module"></script>
321
345
 
322
346
  <!-- Or specific features -->
323
- <script src="/js/hyperclay.js?features=save,admin,toast" type="module"></script>
347
+ <script src="/js/hyperclay.js?features=save,edit-mode-helpers,toast" type="module"></script>
324
348
  ```
325
349
 
326
350
  ## Contributing
@@ -338,6 +362,14 @@ myFeature();
338
362
 
339
363
  MIT © Hyperclay
340
364
 
365
+ ### Third-Party Credits
366
+
367
+ This project includes the following open-source libraries:
368
+
369
+ - **[Idiomorph](https://github.com/bigskysoftware/idiomorph)** - DOM morphing library by Big Sky Software (0BSD)
370
+ - **[Tailwind CSS](https://github.com/tailwindlabs/tailwindcss)** - Utility-first CSS framework by Tailwind Labs (MIT)
371
+ - **[Sortable.js](https://github.com/SortableJS/Sortable)** - Drag-and-drop library (MIT)
372
+
341
373
  ## Links
342
374
 
343
375
  - [Documentation](https://hyperclay.com/docs)
@@ -222,9 +222,12 @@ const behaviorCollector = (() => {
222
222
  };
223
223
  })();
224
224
 
225
- // Self-export to hyperclay only
226
- window.hyperclay = window.hyperclay || {};
227
- window.hyperclay.behaviorCollector = behaviorCollector;
225
+ // Auto-export to window unless suppressed by loader
226
+ if (!window.__hyperclayNoAutoExport) {
227
+ window.hyperclay = window.hyperclay || {};
228
+ window.hyperclay.behaviorCollector = behaviorCollector;
229
+ window.h = window.hyperclay;
230
+ }
228
231
 
229
232
  export default behaviorCollector;
230
233
 
@@ -234,4 +237,4 @@ export function init() {
234
237
  }
235
238
 
236
239
  // Auto-init when module is imported
237
- init();
240
+ init();
@@ -47,8 +47,11 @@ function sendMessage(eventOrObj, successMessage = "Successfully sent", callback)
47
47
  });
48
48
  }
49
49
 
50
- // Self-export to hyperclay only
51
- window.hyperclay = window.hyperclay || {};
52
- window.hyperclay.sendMessage = sendMessage;
50
+ // Auto-export to window unless suppressed by loader
51
+ if (!window.__hyperclayNoAutoExport) {
52
+ window.hyperclay = window.hyperclay || {};
53
+ window.hyperclay.sendMessage = sendMessage;
54
+ window.h = window.hyperclay;
55
+ }
53
56
 
54
- export default sendMessage;
57
+ export default sendMessage;
@@ -347,8 +347,11 @@ function detectContentType(content) {
347
347
  return { type: "txt", mime: "text/plain", extension: ".txt" };
348
348
  }
349
349
 
350
- // Self-export to hyperclay only
351
- window.hyperclay = window.hyperclay || {};
352
- window.hyperclay.uploadFile = uploadFile;
353
- window.hyperclay.createFile = createFile;
354
- window.hyperclay.uploadFileBasic = uploadFileBasic;
350
+ // Auto-export to window unless suppressed by loader
351
+ if (!window.__hyperclayNoAutoExport) {
352
+ window.hyperclay = window.hyperclay || {};
353
+ window.hyperclay.uploadFile = uploadFile;
354
+ window.hyperclay.createFile = createFile;
355
+ window.hyperclay.uploadFileBasic = uploadFileBasic;
356
+ window.h = window.hyperclay;
357
+ }
package/core/autosave.js CHANGED
@@ -4,60 +4,22 @@
4
4
  * Automatically saves page on DOM changes with throttling.
5
5
  * Warns before leaving page with unsaved changes.
6
6
  *
7
- * Requires the 'save' module to be loaded first.
7
+ * Requires the 'save-system' module to be loaded first.
8
8
  */
9
9
 
10
10
  import toast from "../ui/toast.js";
11
- import throttle from "../utilities/throttle.js";
12
11
  import Mutation from "../utilities/mutation.js";
13
12
  import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
14
- import { getPageContents } from "./savePageCore.js";
15
13
  import {
16
- savePage,
17
- getUnsavedChanges,
18
- setUnsavedChanges,
19
- getLastSavedContents
14
+ savePageThrottled,
15
+ getUnsavedChanges
20
16
  } from "./savePage.js";
21
17
 
22
- let baselineContents = '';
23
-
24
- // Capture baseline after setup mutations settle
25
- document.addEventListener('DOMContentLoaded', () => {
26
- if (isEditMode) {
27
- setTimeout(() => {
28
- baselineContents = getPageContents();
29
- }, 1500);
30
- }
31
- });
32
-
33
- /**
34
- * Throttled version of savePage for auto-save
35
- */
36
- const throttledSave = throttle(savePage, 1200);
37
-
38
- /**
39
- * Save the page with throttling, for use with auto-save
40
- * Checks both baseline and last saved content to prevent saves from initial setup
41
- *
42
- * @param {Function} callback - Optional callback
43
- */
44
- export function savePageThrottled(callback = () => {}) {
45
- if (!isEditMode) return;
46
-
47
- const currentContents = getPageContents();
48
- // For autosave: check both that content changed from baseline AND from last save
49
- // This prevents saves from initial setup mutations
50
- if (currentContents !== baselineContents && currentContents !== getLastSavedContents()) {
51
- setUnsavedChanges(true);
52
- throttledSave(callback);
53
- }
54
- }
55
-
56
18
  /**
57
19
  * Initialize auto-save on DOM changes
58
20
  * Uses debounced mutation observer
59
21
  */
60
- export function initSavePageOnChange() {
22
+ function initSavePageOnChange() {
61
23
  Mutation.onAnyChange({
62
24
  debounce: 3333,
63
25
  omitChangeDetails: true
@@ -83,13 +45,9 @@ function init() {
83
45
  initSavePageOnChange();
84
46
  }
85
47
 
86
- // Self-export to hyperclay
87
- window.hyperclay = window.hyperclay || {};
88
- window.hyperclay.savePageThrottled = savePageThrottled;
89
- window.hyperclay.initSavePageOnChange = initSavePageOnChange;
48
+ // No window exports - savePageThrottled is exported from save-system
90
49
 
91
50
  // Auto-init when module is imported
92
51
  init();
93
52
 
94
- export { init, savePageThrottled, initSavePageOnChange };
95
53
  export default init;
@@ -7,11 +7,14 @@ function init() {
7
7
  initPageType();
8
8
  }
9
9
 
10
- // Self-export to hyperclay only
11
- window.hyperclay = window.hyperclay || {};
12
- window.hyperclay.toggleEditMode = toggleEditMode;
13
- window.hyperclay.isEditMode = isEditMode;
14
- window.hyperclay.isOwner = isOwner;
10
+ // Auto-export to window unless suppressed by loader
11
+ if (!window.__hyperclayNoAutoExport) {
12
+ window.hyperclay = window.hyperclay || {};
13
+ window.hyperclay.toggleEditMode = toggleEditMode;
14
+ window.hyperclay.isEditMode = isEditMode;
15
+ window.hyperclay.isOwner = isOwner;
16
+ window.h = window.hyperclay;
17
+ }
15
18
 
16
19
  // Auto-init when module is imported
17
20
  init();
@@ -61,9 +61,12 @@ export function init() {
61
61
  enablePersistentFormInputValues("[persist]");
62
62
  }
63
63
 
64
- // Self-export to hyperclay only
65
- window.hyperclay = window.hyperclay || {};
66
- window.hyperclay.enablePersistentFormInputValues = enablePersistentFormInputValues;
64
+ // Auto-export to window unless suppressed by loader
65
+ if (!window.__hyperclayNoAutoExport) {
66
+ window.hyperclay = window.hyperclay || {};
67
+ window.hyperclay.enablePersistentFormInputValues = enablePersistentFormInputValues;
68
+ window.h = window.hyperclay;
69
+ }
67
70
 
68
71
  // Auto-init when module is imported
69
- init();
72
+ init();
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Export to Window Module
3
+ *
4
+ * When loaded FIRST by the loader, this flips the __hyperclayNoAutoExport flag
5
+ * to false, allowing subsequent modules to self-export to window.hyperclay.
6
+ *
7
+ * This module is included in all presets by default.
8
+ * Exclude it if you prefer ES module-only imports (no window pollution).
9
+ */
10
+
11
+ // Flip the flag so modules will auto-export
12
+ window.__hyperclayNoAutoExport = false;
13
+
14
+ export default true;
@@ -152,10 +152,13 @@ const optionVisibilityRuleGenerator = {
152
152
  },
153
153
  };
154
154
 
155
- // Self-export to window and hyperclay
156
- window.optionVisibilityRuleGenerator = optionVisibilityRuleGenerator;
157
- window.hyperclay = window.hyperclay || {};
158
- window.hyperclay.optionVisibilityRuleGenerator = optionVisibilityRuleGenerator;
155
+ // Auto-export to window unless suppressed by loader
156
+ if (!window.__hyperclayNoAutoExport) {
157
+ window.optionVisibilityRuleGenerator = optionVisibilityRuleGenerator;
158
+ window.hyperclay = window.hyperclay || {};
159
+ window.hyperclay.optionVisibilityRuleGenerator = optionVisibilityRuleGenerator;
160
+ window.h = window.hyperclay;
161
+ }
159
162
 
160
163
  export default optionVisibilityRuleGenerator;
161
164
 
@@ -165,4 +168,4 @@ export function init() {
165
168
  }
166
169
 
167
170
  // Auto-init when module is imported
168
- init();
171
+ init();
package/core/savePage.js CHANGED
@@ -10,18 +10,17 @@
10
10
  */
11
11
 
12
12
  import toast from "../ui/toast.js";
13
+ import throttle from "../utilities/throttle.js";
13
14
  import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
14
15
  import {
15
16
  savePage as savePageCore,
16
17
  getPageContents,
17
- replacePageWith as replacePageWithCore
18
+ replacePageWith as replacePageWithCore,
19
+ beforeSave
18
20
  } from "./savePageCore.js";
19
21
 
20
- // Re-export beforeSave from core for backward compatibility
21
- export { beforeSave } from "./savePageCore.js";
22
-
23
- // Re-export getPageContents for autosave module
24
- export { getPageContents } from "./savePageCore.js";
22
+ // Re-export from core for backward compatibility
23
+ export { beforeSave, getPageContents };
25
24
 
26
25
  let unsavedChanges = false;
27
26
  let lastSavedContents = '';
@@ -97,6 +96,39 @@ export function replacePageWith(url) {
97
96
  });
98
97
  }
99
98
 
99
+ // Throttled version of savePage for auto-save
100
+ const throttledSave = throttle(savePage, 1200);
101
+
102
+ // Baseline for autosave comparison
103
+ let baselineContents = '';
104
+
105
+ // Capture baseline after setup mutations settle
106
+ document.addEventListener('DOMContentLoaded', () => {
107
+ if (isEditMode) {
108
+ setTimeout(() => {
109
+ baselineContents = getPageContents();
110
+ }, 1500);
111
+ }
112
+ });
113
+
114
+ /**
115
+ * Save the page with throttling, for use with auto-save
116
+ * Checks both baseline and last saved content to prevent saves from initial setup
117
+ *
118
+ * @param {Function} callback - Optional callback
119
+ */
120
+ export function savePageThrottled(callback = () => {}) {
121
+ if (!isEditMode) return;
122
+
123
+ const currentContents = getPageContents();
124
+ // For autosave: check both that content changed from baseline AND from last save
125
+ // This prevents saves from initial setup mutations
126
+ if (currentContents !== baselineContents && currentContents !== lastSavedContents) {
127
+ unsavedChanges = true;
128
+ throttledSave(callback);
129
+ }
130
+ }
131
+
100
132
  /**
101
133
  * Initialize keyboard shortcut for save (CMD/CTRL+S)
102
134
  */
@@ -138,16 +170,17 @@ export function init() {
138
170
  initHyperclaySaveButton();
139
171
  }
140
172
 
141
- // Self-export to hyperclay only
142
- window.hyperclay = window.hyperclay || {};
143
- window.hyperclay.savePage = savePage;
144
- window.hyperclay.beforeSave = beforeSave;
145
- window.hyperclay.replacePageWith = replacePageWith;
146
- window.hyperclay.initHyperclaySaveButton = initHyperclaySaveButton;
147
- window.hyperclay.initSaveKeyboardShortcut = initSaveKeyboardShortcut;
173
+ // Auto-export to window unless suppressed by loader
174
+ if (!window.__hyperclayNoAutoExport) {
175
+ window.hyperclay = window.hyperclay || {};
176
+ window.hyperclay.savePage = savePage;
177
+ window.hyperclay.savePageThrottled = savePageThrottled;
178
+ window.hyperclay.beforeSave = beforeSave;
179
+ window.hyperclay.replacePageWith = replacePageWith;
180
+ window.h = window.hyperclay;
181
+ }
148
182
 
149
183
  // Auto-init when module is imported
150
184
  init();
151
185
 
152
- export { savePage, replacePageWith, initSaveKeyboardShortcut, initHyperclaySaveButton, init };
153
186
  export default savePage;
@@ -220,10 +220,13 @@ export function replacePageWith(url, callback = () => {}) {
220
220
  });
221
221
  }
222
222
 
223
- // Self-export to hyperclay only
224
- window.hyperclay = window.hyperclay || {};
225
- window.hyperclay.savePage = savePage;
226
- window.hyperclay.saveHtml = saveHtml;
227
- window.hyperclay.replacePageWith = replacePageWith;
228
- window.hyperclay.beforeSave = beforeSave;
229
- window.hyperclay.getPageContents = getPageContents;
223
+ // Auto-export to window unless suppressed by loader
224
+ if (!window.__hyperclayNoAutoExport) {
225
+ window.hyperclay = window.hyperclay || {};
226
+ window.hyperclay.savePage = savePage;
227
+ window.hyperclay.saveHtml = saveHtml;
228
+ window.hyperclay.replacePageWith = replacePageWith;
229
+ window.hyperclay.beforeSave = beforeSave;
230
+ window.hyperclay.getPageContents = getPageContents;
231
+ window.h = window.hyperclay;
232
+ }
@@ -172,10 +172,13 @@ function init () {
172
172
 
173
173
  }
174
174
 
175
- // Self-export to window and hyperclay
176
- window.initCustomAttributes = init;
177
- window.hyperclay = window.hyperclay || {};
178
- window.hyperclay.initCustomAttributes = init;
175
+ // Auto-export to window unless suppressed by loader
176
+ if (!window.__hyperclayNoAutoExport) {
177
+ window.initCustomAttributes = init;
178
+ window.hyperclay = window.hyperclay || {};
179
+ window.hyperclay.initCustomAttributes = init;
180
+ window.h = window.hyperclay;
181
+ }
179
182
 
180
183
  // Auto-init when module is imported
181
184
  init();
@@ -8,12 +8,16 @@
8
8
  - add `onsorted` attribute to execute code when items are sorted
9
9
  - e.g. <ul sortable onsorted="console.log('Items reordered!')"></ul>
10
10
 
11
+ This wrapper conditionally loads the full Sortable.js vendor script (~118KB)
12
+ only when in edit mode. The script is injected with save-ignore so it's
13
+ stripped from the page before saving.
14
+
11
15
  */
12
- import { isEditMode, isOwner } from "../core/isAdminOfCurrentResource.js";
16
+ import { isEditMode } from "../core/isAdminOfCurrentResource.js";
13
17
  import Mutation from "../utilities/mutation.js";
14
- import Sortable from '../vendor/Sortable.js';
18
+ import { loadVendorScript, getVendorUrl } from "../utilities/loadVendorScript.js";
15
19
 
16
- function makeSortable (sortableElem) {
20
+ function makeSortable(sortableElem, Sortable) {
17
21
  let options = {};
18
22
 
19
23
  // Check if Sortable instance already exists
@@ -51,11 +55,23 @@ function makeSortable (sortableElem) {
51
55
  Sortable.create(sortableElem, options);
52
56
  }
53
57
 
54
- function init () {
58
+ async function init() {
55
59
  if (!isEditMode) return;
56
60
 
61
+ // Load the vendor script
62
+ const vendorUrl = getVendorUrl(import.meta.url, '../vendor/Sortable.vendor.js');
63
+ const Sortable = await loadVendorScript(vendorUrl, 'Sortable');
64
+
65
+ // Auto-export to window unless suppressed by loader
66
+ if (!window.__hyperclayNoAutoExport) {
67
+ window.Sortable = Sortable;
68
+ window.hyperclay = window.hyperclay || {};
69
+ window.hyperclay.Sortable = Sortable;
70
+ window.h = window.hyperclay;
71
+ }
72
+
57
73
  // Set up sortable on page load
58
- document.querySelectorAll('[sortable]').forEach(makeSortable);
74
+ document.querySelectorAll('[sortable]').forEach(el => makeSortable(el, Sortable));
59
75
 
60
76
  // Set up listener for dynamically added elements
61
77
  Mutation.onAddElement({
@@ -63,22 +79,13 @@ function init () {
63
79
  debounce: 200
64
80
  }, (changes) => {
65
81
  changes.forEach(({ element }) => {
66
- makeSortable(element);
82
+ makeSortable(element, Sortable);
67
83
  });
68
84
  });
69
-
70
- // ❗️re-initializing sortable on parent elements isn't necessary
71
- // sortable.js handles this automatically
72
- // ❌ onElementAdded(newElem => makeSortable(newElem.closest('[sortable]')))
73
85
  }
74
86
 
75
- // Self-export to window and hyperclay
76
- window.Sortable = Sortable;
77
- window.hyperclay = window.hyperclay || {};
78
- window.hyperclay.Sortable = Sortable;
79
-
80
87
  // Auto-init when module is imported
81
88
  init();
82
89
 
83
- export { init, Sortable };
90
+ export { init };
84
91
  export default init;