hyperclayjs 1.23.0 → 1.24.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.
- package/README.md +12 -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/custom-attributes/saveFreeze.js +69 -0
- package/src/hyperclay.js +5 -1
- package/src/ui/theModal.js +26 -0
- package/src/ui/toast.js +2 -10
- package/src/utilities/mutation.js +7 -1
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() |
|
|
@@ -79,6 +79,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
79
79
|
| input-helpers | 3.9KB | [prevent-enter], [autosize] for textareas |
|
|
80
80
|
| movable | 2.5KB | Free-positioning drag with [movable] and [movable-handle], edit mode only |
|
|
81
81
|
| onaftersave | 1KB | [onaftersave] attribute - run JS when save status changes |
|
|
82
|
+
| save-freeze | 1.9KB | [save-freeze] attribute - freeze element innerHTML for saves, live DOM changes freely |
|
|
82
83
|
| sortable | 3.4KB | Drag-drop sorting with [sortable], lazy-loads ~118KB Sortable.js in edit mode |
|
|
83
84
|
|
|
84
85
|
### UI Components (User interface elements)
|
|
@@ -86,8 +87,8 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
86
87
|
| Module | Size | Description |
|
|
87
88
|
|--------|------|-------------|
|
|
88
89
|
| dialogs | 7.7KB | ask(), consent(), tell(), snippet() dialog functions |
|
|
89
|
-
| the-modal |
|
|
90
|
-
| toast | 15.
|
|
90
|
+
| the-modal | 22.4KB | Full modal window creation system - window.theModal |
|
|
91
|
+
| toast | 15.8KB | Success/error message notifications, toast(msg, msgType) |
|
|
91
92
|
|
|
92
93
|
### Utilities (Core utilities (often auto-included))
|
|
93
94
|
|
|
@@ -96,7 +97,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
96
97
|
| cache-bust | 0.6KB | Cache-bust href/src attributes |
|
|
97
98
|
| cookie | 1.4KB | Cookie management (often auto-included) |
|
|
98
99
|
| debounce | 0.4KB | Function debouncing |
|
|
99
|
-
| mutation | 13.
|
|
100
|
+
| mutation | 13.8KB | DOM mutation observation (often auto-included) |
|
|
100
101
|
| nearest | 3.4KB | Find nearest elements (often auto-included) |
|
|
101
102
|
| throttle | 0.8KB | Function throttling |
|
|
102
103
|
|
|
@@ -122,7 +123,7 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
122
123
|
| Module | Size | Description |
|
|
123
124
|
|--------|------|-------------|
|
|
124
125
|
| file-upload | 10.7KB | File upload with progress |
|
|
125
|
-
| live-sync | 11.
|
|
126
|
+
| live-sync | 11.7KB | Real-time DOM sync across browsers |
|
|
126
127
|
| send-message | 1.3KB | Message sending utility |
|
|
127
128
|
|
|
128
129
|
### Vendor Libraries (Third-party libraries)
|
|
@@ -133,17 +134,17 @@ import 'hyperclayjs/presets/standard.js';
|
|
|
133
134
|
|
|
134
135
|
## Presets
|
|
135
136
|
|
|
136
|
-
### Minimal (~
|
|
137
|
+
### Minimal (~57KB)
|
|
137
138
|
Essential features for basic editing
|
|
138
139
|
|
|
139
140
|
**Modules:** `save-core`, `snapshot`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
|
|
140
141
|
|
|
141
|
-
### Standard (~
|
|
142
|
+
### Standard (~79.2KB)
|
|
142
143
|
Standard feature set for most use cases
|
|
143
144
|
|
|
144
145
|
**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
146
|
|
|
146
|
-
### Everything (~
|
|
147
|
+
### Everything (~219.6KB)
|
|
147
148
|
All available features
|
|
148
149
|
|
|
149
150
|
Includes all available modules across all categories.
|
|
@@ -316,9 +317,9 @@ tell("Welcome to Hyperclay!");
|
|
|
316
317
|
|
|
317
318
|
```html
|
|
318
319
|
<!-- Only visible/editable in edit mode -->
|
|
319
|
-
<div contenteditable
|
|
320
|
-
<input type="text"
|
|
321
|
-
<script
|
|
320
|
+
<div contenteditable editmode:contenteditable>Admin can edit this</div>
|
|
321
|
+
<input type="text" viewmode:disabled>
|
|
322
|
+
<script editmode:resource>console.log('Admin only');</script>
|
|
322
323
|
```
|
|
323
324
|
|
|
324
325
|
## 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 => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [save-freeze] Custom Attribute
|
|
3
|
+
*
|
|
4
|
+
* Freezes an element's innerHTML for save purposes.
|
|
5
|
+
* The live DOM can change freely, but the saved HTML always
|
|
6
|
+
* contains the original content captured when the element first appeared.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* <div save-freeze>Content that JS will modify at runtime</div>
|
|
10
|
+
*
|
|
11
|
+
* The original innerHTML is captured:
|
|
12
|
+
* - On page load, for all existing [save-freeze] elements
|
|
13
|
+
* - On DOM insertion, for dynamically added [save-freeze] elements
|
|
14
|
+
*
|
|
15
|
+
* At save time, the clone's innerHTML is replaced with the stored original.
|
|
16
|
+
* Changes inside [save-freeze] elements do not trigger autosave dirty checks.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { onPrepareForSave } from "../core/snapshot.js";
|
|
20
|
+
import { isEditMode } from "../core/isAdminOfCurrentResource.js";
|
|
21
|
+
|
|
22
|
+
const originals = new WeakMap();
|
|
23
|
+
|
|
24
|
+
function capture(el) {
|
|
25
|
+
if (!originals.has(el)) {
|
|
26
|
+
originals.set(el, el.innerHTML);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function captureAll() {
|
|
31
|
+
for (const el of document.querySelectorAll('[save-freeze]')) {
|
|
32
|
+
capture(el);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function init() {
|
|
37
|
+
if (!isEditMode) return;
|
|
38
|
+
|
|
39
|
+
captureAll();
|
|
40
|
+
|
|
41
|
+
const observer = new MutationObserver(mutations => {
|
|
42
|
+
for (const m of mutations) {
|
|
43
|
+
for (const node of m.addedNodes) {
|
|
44
|
+
if (node.nodeType !== 1) continue;
|
|
45
|
+
if (node.hasAttribute?.('save-freeze')) capture(node);
|
|
46
|
+
for (const el of node.querySelectorAll?.('[save-freeze]') || []) {
|
|
47
|
+
capture(el);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
53
|
+
|
|
54
|
+
onPrepareForSave(clone => {
|
|
55
|
+
const liveElements = document.querySelectorAll('[save-freeze]');
|
|
56
|
+
const cloneElements = clone.querySelectorAll('[save-freeze]');
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < cloneElements.length; i++) {
|
|
59
|
+
const liveEl = liveElements[i];
|
|
60
|
+
if (liveEl && originals.has(liveEl)) {
|
|
61
|
+
cloneElements[i].innerHTML = originals.get(liveEl);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
init();
|
|
68
|
+
|
|
69
|
+
export default init;
|
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.
|
|
4
|
+
* HyperclayJS v1.24.0 - 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.
|
|
@@ -44,6 +44,7 @@ const MODULE_PATHS = {
|
|
|
44
44
|
"dom-helpers": "./custom-attributes/domHelpers.js",
|
|
45
45
|
"input-helpers": "./custom-attributes/inputHelpers.js",
|
|
46
46
|
"onaftersave": "./custom-attributes/onaftersave.js",
|
|
47
|
+
"save-freeze": "./custom-attributes/saveFreeze.js",
|
|
47
48
|
"dialogs": "./ui/prompts.js",
|
|
48
49
|
"toast": "./ui/toast.js",
|
|
49
50
|
"toast-hyperclay": "./ui/toast-hyperclay.js",
|
|
@@ -124,6 +125,7 @@ const PRESETS = {
|
|
|
124
125
|
"dom-helpers",
|
|
125
126
|
"input-helpers",
|
|
126
127
|
"onaftersave",
|
|
128
|
+
"save-freeze",
|
|
127
129
|
"dialogs",
|
|
128
130
|
"toast",
|
|
129
131
|
"the-modal",
|
|
@@ -168,6 +170,7 @@ const PRESETS = {
|
|
|
168
170
|
"dom-helpers",
|
|
169
171
|
"input-helpers",
|
|
170
172
|
"onaftersave",
|
|
173
|
+
"save-freeze",
|
|
171
174
|
"dialogs",
|
|
172
175
|
"toast",
|
|
173
176
|
"the-modal",
|
|
@@ -208,6 +211,7 @@ const EDIT_MODE_ONLY = new Set([
|
|
|
208
211
|
"sortable",
|
|
209
212
|
"movable",
|
|
210
213
|
"onaftersave",
|
|
214
|
+
"save-freeze",
|
|
211
215
|
"cache-bust",
|
|
212
216
|
"hyper-morph",
|
|
213
217
|
"file-upload",
|
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
|
},
|
|
@@ -199,7 +204,8 @@ const Mutation = {
|
|
|
199
204
|
while (element && element.nodeType === 1) {
|
|
200
205
|
if (element.hasAttribute?.('mutations-ignore') ||
|
|
201
206
|
element.hasAttribute?.('save-remove') ||
|
|
202
|
-
element.hasAttribute?.('save-ignore')
|
|
207
|
+
element.hasAttribute?.('save-ignore') ||
|
|
208
|
+
element.hasAttribute?.('save-freeze')) {
|
|
203
209
|
return true;
|
|
204
210
|
}
|
|
205
211
|
element = element.parentElement;
|