hyperclayjs 1.23.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/package.json +1 -1
- package/src/communication/live-sync.js +3 -1
- package/src/core/adminContenteditable.js +1 -1
- package/src/core/adminInputs.js +4 -43
- package/src/core/adminOnClick.js +1 -1
- package/src/core/adminResources.js +2 -2
- package/src/hyperclay.js +1 -1
- package/src/ui/theModal.js +26 -0
- package/src/ui/toast.js +2 -10
- package/src/utilities/mutation.js +5 -0
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
59
59
|
|--------|------|-------------|
|
|
60
60
|
| autosave | 1.4KB | Auto-save on DOM changes |
|
|
61
61
|
| edit-mode | 1.8KB | Toggle edit mode on hyperclay on/off |
|
|
62
|
-
| edit-mode-helpers |
|
|
62
|
+
| edit-mode-helpers | 6.8KB | Admin-only functionality: [viewmode:disabled], [editmode:resource], [editmode:onclick] |
|
|
63
63
|
| option-visibility | 7.1KB | Dynamic show/hide based on ancestor state with option:attribute="value" |
|
|
64
64
|
| persist | 2.4KB | Persist input/select/textarea values to the DOM with [persist] attribute |
|
|
65
65
|
| save-core | 8.9KB | Basic save function only - hyperclay.savePage() |
|
|
@@ -86,8 +86,8 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
86
86
|
| Module | Size | Description |
|
|
87
87
|
|--------|------|-------------|
|
|
88
88
|
| dialogs | 7.7KB | ask(), consent(), tell(), snippet() dialog functions |
|
|
89
|
-
| the-modal |
|
|
90
|
-
| toast | 15.
|
|
89
|
+
| the-modal | 22.4KB | Full modal window creation system - window.theModal |
|
|
90
|
+
| toast | 15.8KB | Success/error message notifications, toast(msg, msgType) |
|
|
91
91
|
|
|
92
92
|
### Utilities (Core utilities (often auto-included))
|
|
93
93
|
|
|
@@ -96,7 +96,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
96
96
|
| cache-bust | 0.6KB | Cache-bust href/src attributes |
|
|
97
97
|
| cookie | 1.4KB | Cookie management (often auto-included) |
|
|
98
98
|
| debounce | 0.4KB | Function debouncing |
|
|
99
|
-
| mutation | 13.
|
|
99
|
+
| mutation | 13.7KB | DOM mutation observation (often auto-included) |
|
|
100
100
|
| nearest | 3.4KB | Find nearest elements (often auto-included) |
|
|
101
101
|
| throttle | 0.8KB | Function throttling |
|
|
102
102
|
|
|
@@ -122,7 +122,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
122
122
|
| Module | Size | Description |
|
|
123
123
|
|--------|------|-------------|
|
|
124
124
|
| file-upload | 10.7KB | File upload with progress |
|
|
125
|
-
| live-sync | 11.
|
|
125
|
+
| live-sync | 11.7KB | Real-time DOM sync across browsers |
|
|
126
126
|
| send-message | 1.3KB | Message sending utility |
|
|
127
127
|
|
|
128
128
|
### Vendor Libraries (Third-party libraries)
|
|
@@ -133,17 +133,17 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
133
133
|
|
|
134
134
|
## Presets
|
|
135
135
|
|
|
136
|
-
### Minimal (~
|
|
136
|
+
### Minimal (~57KB)
|
|
137
137
|
Essential features for basic editing
|
|
138
138
|
|
|
139
139
|
**Modules:** `save-core`, `snapshot`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
|
|
140
140
|
|
|
141
|
-
### Standard (~
|
|
141
|
+
### Standard (~79.2KB)
|
|
142
142
|
Standard feature set for most use cases
|
|
143
143
|
|
|
144
144
|
**Modules:** `save-core`, `snapshot`, `save-system`, `unsaved-warning`, `edit-mode-helpers`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
|
|
145
145
|
|
|
146
|
-
### Everything (~217.
|
|
146
|
+
### Everything (~217.6KB)
|
|
147
147
|
All available features
|
|
148
148
|
|
|
149
149
|
Includes all available modules across all categories.
|
|
@@ -316,9 +316,9 @@ tell("Welcome to Hyperclay!");
|
|
|
316
316
|
|
|
317
317
|
```html
|
|
318
318
|
<!-- Only visible/editable in edit mode -->
|
|
319
|
-
<div contenteditable
|
|
320
|
-
<input type="text"
|
|
321
|
-
<script
|
|
319
|
+
<div contenteditable editmode:contenteditable>Admin can edit this</div>
|
|
320
|
+
<input type="text" viewmode:disabled>
|
|
321
|
+
<script editmode:resource>console.log('Admin only');</script>
|
|
322
322
|
```
|
|
323
323
|
|
|
324
324
|
## Module Creation
|
package/package.json
CHANGED
|
@@ -321,7 +321,9 @@ class LiveSync {
|
|
|
321
321
|
|
|
322
322
|
this._log('applyUpdate - morph complete, resuming mutations');
|
|
323
323
|
Mutation.resume();
|
|
324
|
-
|
|
324
|
+
// Defer past microtask boundary — MutationObserver callbacks and any async
|
|
325
|
+
// morph side-effects fire before this, so isPaused catches stray snapshots
|
|
326
|
+
setTimeout(() => { this.isPaused = false; }, 0);
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
/**
|
|
@@ -2,7 +2,7 @@ import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
|
|
|
2
2
|
import onDomReady from "../dom-utilities/onDomReady.js";
|
|
3
3
|
import {beforeSave} from "./savePage.js";
|
|
4
4
|
|
|
5
|
-
const SELECTOR = '[
|
|
5
|
+
const SELECTOR = '[editmode\\:contenteditable]';
|
|
6
6
|
|
|
7
7
|
export function disableContentEditableBeforeSave () {
|
|
8
8
|
beforeSave(docElem => {
|
package/src/core/adminInputs.js
CHANGED
|
@@ -2,20 +2,13 @@ import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
|
|
|
2
2
|
import onDomReady from "../dom-utilities/onDomReady.js";
|
|
3
3
|
import { beforeSave } from "./savePage.js";
|
|
4
4
|
|
|
5
|
-
const SELECTOR_DISABLED = '[
|
|
5
|
+
const SELECTOR_DISABLED = '[viewmode\\:disabled]';
|
|
6
6
|
const SELECTOR_READONLY = '[viewmode\\:readonly]';
|
|
7
|
-
const SELECTOR_ALL = '[edit-mode-input], [viewmode\\:disabled], [viewmode\\:readonly]';
|
|
8
7
|
|
|
9
8
|
export function disableAdminInputsBeforeSave() {
|
|
10
9
|
beforeSave(docElem => {
|
|
11
10
|
docElem.querySelectorAll(SELECTOR_DISABLED).forEach(input => {
|
|
12
|
-
|
|
13
|
-
input.setAttribute('disabled', '');
|
|
14
|
-
} else if (supportsReadonly(input)) {
|
|
15
|
-
input.setAttribute('readonly', '');
|
|
16
|
-
} else {
|
|
17
|
-
input.setAttribute('disabled', '');
|
|
18
|
-
}
|
|
11
|
+
input.setAttribute('disabled', '');
|
|
19
12
|
});
|
|
20
13
|
docElem.querySelectorAll(SELECTOR_READONLY).forEach(input => {
|
|
21
14
|
input.setAttribute('readonly', '');
|
|
@@ -31,16 +24,9 @@ export function enableAdminInputsOnPageLoad() {
|
|
|
31
24
|
});
|
|
32
25
|
}
|
|
33
26
|
|
|
34
|
-
// Runtime toggle functions
|
|
35
27
|
export function enableAdminInputs() {
|
|
36
28
|
document.querySelectorAll(SELECTOR_DISABLED).forEach(input => {
|
|
37
|
-
|
|
38
|
-
input.removeAttribute('disabled');
|
|
39
|
-
} else if (supportsReadonly(input)) {
|
|
40
|
-
input.removeAttribute('readonly');
|
|
41
|
-
} else {
|
|
42
|
-
input.removeAttribute('disabled');
|
|
43
|
-
}
|
|
29
|
+
input.removeAttribute('disabled');
|
|
44
30
|
});
|
|
45
31
|
document.querySelectorAll(SELECTOR_READONLY).forEach(input => {
|
|
46
32
|
input.removeAttribute('readonly');
|
|
@@ -49,43 +35,18 @@ export function enableAdminInputs() {
|
|
|
49
35
|
|
|
50
36
|
export function disableAdminInputs() {
|
|
51
37
|
document.querySelectorAll(SELECTOR_DISABLED).forEach(input => {
|
|
52
|
-
|
|
53
|
-
input.setAttribute('disabled', '');
|
|
54
|
-
} else if (supportsReadonly(input)) {
|
|
55
|
-
input.setAttribute('readonly', '');
|
|
56
|
-
} else {
|
|
57
|
-
input.setAttribute('disabled', '');
|
|
58
|
-
}
|
|
38
|
+
input.setAttribute('disabled', '');
|
|
59
39
|
});
|
|
60
40
|
document.querySelectorAll(SELECTOR_READONLY).forEach(input => {
|
|
61
41
|
input.setAttribute('readonly', '');
|
|
62
42
|
});
|
|
63
43
|
}
|
|
64
44
|
|
|
65
|
-
// Input types that support the readonly attribute
|
|
66
|
-
const readonlyTypes = ['text', 'search', 'url', 'tel', 'email', 'password', 'date', 'month', 'week', 'time', 'datetime-local', 'number'];
|
|
67
|
-
|
|
68
|
-
function supportsReadonly(element) {
|
|
69
|
-
const tagName = element.tagName?.toUpperCase();
|
|
70
|
-
|
|
71
|
-
if (tagName === 'TEXTAREA') return true;
|
|
72
|
-
if (tagName === 'SELECT' || tagName === 'BUTTON' || tagName === 'FIELDSET') return false;
|
|
73
|
-
|
|
74
|
-
if (tagName === 'INPUT') {
|
|
75
|
-
const type = element.type || 'text';
|
|
76
|
-
return readonlyTypes.includes(type);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Auto-initialize
|
|
83
45
|
export function init() {
|
|
84
46
|
disableAdminInputsBeforeSave();
|
|
85
47
|
enableAdminInputsOnPageLoad();
|
|
86
48
|
}
|
|
87
49
|
|
|
88
|
-
// Export to window
|
|
89
50
|
window.hyperclay = window.hyperclay || {};
|
|
90
51
|
window.hyperclay.enableAdminInputs = enableAdminInputs;
|
|
91
52
|
window.hyperclay.disableAdminInputs = disableAdminInputs;
|
package/src/core/adminOnClick.js
CHANGED
|
@@ -2,7 +2,7 @@ import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
|
|
|
2
2
|
import onDomReady from "../dom-utilities/onDomReady.js";
|
|
3
3
|
import {beforeSave} from "./savePage.js";
|
|
4
4
|
|
|
5
|
-
const SELECTOR = '[
|
|
5
|
+
const SELECTOR = '[editmode\\:onclick]';
|
|
6
6
|
|
|
7
7
|
export function disableOnClickBeforeSave () {
|
|
8
8
|
beforeSave(docElem => {
|
|
@@ -2,8 +2,8 @@ import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
|
|
|
2
2
|
import onDomReady from "../dom-utilities/onDomReady.js";
|
|
3
3
|
import {beforeSave} from "./savePage.js";
|
|
4
4
|
|
|
5
|
-
const SELECTOR = '[
|
|
6
|
-
const SELECTOR_INERT = '[
|
|
5
|
+
const SELECTOR = '[editmode\\:resource]:is(style, link, script)';
|
|
6
|
+
const SELECTOR_INERT = '[editmode\\:resource]:is(style, link, script)[type^="inert/"]';
|
|
7
7
|
|
|
8
8
|
export function disableAdminResourcesBeforeSave () {
|
|
9
9
|
beforeSave(docElem => {
|
package/src/hyperclay.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DO NOT EDIT THIS FILE DIRECTLY — it is generated from build/hyperclay.template.js
|
|
3
3
|
*
|
|
4
|
-
* HyperclayJS v1.23.
|
|
4
|
+
* HyperclayJS v1.23.1 - Minimal Browser-Native Loader
|
|
5
5
|
*
|
|
6
6
|
* Modules auto-init when imported (no separate init call needed).
|
|
7
7
|
* Include `export-to-window` feature to export to window.hyperclay.
|
package/src/ui/theModal.js
CHANGED
|
@@ -608,6 +608,24 @@ const themodal = (() => {
|
|
|
608
608
|
const themodalMain = {
|
|
609
609
|
isShowing: false,
|
|
610
610
|
open() {
|
|
611
|
+
// Clean up stale modal if DOM was removed externally (e.g. live-sync morph)
|
|
612
|
+
if (this.isShowing && !document.querySelector('.micromodal-parent')) {
|
|
613
|
+
this._cleanupListeners?.();
|
|
614
|
+
html = "";
|
|
615
|
+
yes = "";
|
|
616
|
+
no = "";
|
|
617
|
+
zIndex = "100";
|
|
618
|
+
closeHtml = "";
|
|
619
|
+
enableClickOutsideCloses = true;
|
|
620
|
+
disableScroll = true;
|
|
621
|
+
disableFocus = false;
|
|
622
|
+
onYes = [];
|
|
623
|
+
onNo = [];
|
|
624
|
+
onOpen = [];
|
|
625
|
+
this.isShowing = false;
|
|
626
|
+
document.body.style.overflow = '';
|
|
627
|
+
}
|
|
628
|
+
|
|
611
629
|
document.body.insertAdjacentHTML("afterbegin", "<div save-remove snapshot-remove class='micromodal-parent'>" + modalCss + modalHtml + "</div>");
|
|
612
630
|
|
|
613
631
|
const modalOverlayElem = document.querySelector(".micromodal__overlay");
|
|
@@ -682,6 +700,13 @@ const themodal = (() => {
|
|
|
682
700
|
document.addEventListener("click", handleClick);
|
|
683
701
|
document.addEventListener("submit", handleSubmit);
|
|
684
702
|
|
|
703
|
+
// Store cleanup so stale listeners can be removed if DOM is yanked externally
|
|
704
|
+
this._cleanupListeners = () => {
|
|
705
|
+
document.removeEventListener("mousedown", handleMousedown);
|
|
706
|
+
document.removeEventListener("click", handleClick);
|
|
707
|
+
document.removeEventListener("submit", handleSubmit);
|
|
708
|
+
};
|
|
709
|
+
|
|
685
710
|
function setButtonsVisibility () {
|
|
686
711
|
modalButtonsElem.classList.toggle("micromodal__hide", !yes && !no);
|
|
687
712
|
modalYesElem.classList.toggle("micromodal__hide", !yes);
|
|
@@ -718,6 +743,7 @@ const themodal = (() => {
|
|
|
718
743
|
document.removeEventListener("mousedown", handleMousedown);
|
|
719
744
|
document.removeEventListener("click", handleClick);
|
|
720
745
|
document.removeEventListener("submit", handleSubmit);
|
|
746
|
+
this._cleanupListeners = null;
|
|
721
747
|
}
|
|
722
748
|
});
|
|
723
749
|
|
package/src/ui/toast.js
CHANGED
|
@@ -235,9 +235,6 @@ export const hyperclayStyles = `
|
|
|
235
235
|
}
|
|
236
236
|
`;
|
|
237
237
|
|
|
238
|
-
// Track which theme styles have been injected
|
|
239
|
-
const injectedThemes = new Set();
|
|
240
|
-
|
|
241
238
|
// Global toast configuration (can be overridden by toast-hyperclay module)
|
|
242
239
|
let toastConfig = {
|
|
243
240
|
styles: modernStyles,
|
|
@@ -253,7 +250,7 @@ export function setToastTheme(config) {
|
|
|
253
250
|
|
|
254
251
|
// Helper function to inject styles for a theme (additive, not replacing)
|
|
255
252
|
export function injectToastStyles(styles, theme) {
|
|
256
|
-
if (
|
|
253
|
+
if (document.querySelector(`style.toast-styles-${theme}`)) return;
|
|
257
254
|
|
|
258
255
|
const styleSheet = document.createElement('style');
|
|
259
256
|
styleSheet.className = `toast-styles-${theme}`;
|
|
@@ -261,8 +258,6 @@ export function injectToastStyles(styles, theme) {
|
|
|
261
258
|
styleSheet.setAttribute('snapshot-remove', '');
|
|
262
259
|
styleSheet.textContent = styles;
|
|
263
260
|
document.head.appendChild(styleSheet);
|
|
264
|
-
|
|
265
|
-
injectedThemes.add(theme);
|
|
266
261
|
}
|
|
267
262
|
|
|
268
263
|
// Core toast function (used by both toast and toastHyperclay)
|
|
@@ -357,8 +352,6 @@ const persistentToastStyles = `
|
|
|
357
352
|
}
|
|
358
353
|
`;
|
|
359
354
|
|
|
360
|
-
let persistentStylesInjected = false;
|
|
361
|
-
|
|
362
355
|
// Track active persistent toasts by message
|
|
363
356
|
const activePersistentToasts = new Map();
|
|
364
357
|
|
|
@@ -367,14 +360,13 @@ function toastPersistent(message, messageType = "warning") {
|
|
|
367
360
|
injectToastStyles(toastConfig.styles, toastConfig.theme);
|
|
368
361
|
|
|
369
362
|
// Inject persistent-specific styles once
|
|
370
|
-
if (!
|
|
363
|
+
if (!document.querySelector('style.toast-styles-persistent')) {
|
|
371
364
|
const styleSheet = document.createElement('style');
|
|
372
365
|
styleSheet.className = 'toast-styles-persistent';
|
|
373
366
|
styleSheet.setAttribute('save-remove', '');
|
|
374
367
|
styleSheet.setAttribute('snapshot-remove', '');
|
|
375
368
|
styleSheet.textContent = persistentToastStyles;
|
|
376
369
|
document.head.appendChild(styleSheet);
|
|
377
|
-
persistentStylesInjected = true;
|
|
378
370
|
}
|
|
379
371
|
|
|
380
372
|
const templates = toastConfig.templates;
|
|
@@ -84,6 +84,11 @@ const Mutation = {
|
|
|
84
84
|
* Resume mutation observation after a pause.
|
|
85
85
|
*/
|
|
86
86
|
resume() {
|
|
87
|
+
// Drain pending mutation records — observer stays connected during pause,
|
|
88
|
+
// so morph mutations are recorded and would fire async after _paused=false
|
|
89
|
+
if (this._observer) {
|
|
90
|
+
this._observer.takeRecords();
|
|
91
|
+
}
|
|
87
92
|
this._paused = false;
|
|
88
93
|
this._log('Resumed');
|
|
89
94
|
},
|