hyperclayjs 1.22.1 → 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 +12 -12
- 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/core/optionVisibility.js +25 -49
- 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,8 +59,8 @@ 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 |
|
|
63
|
-
| option-visibility | 7.
|
|
62
|
+
| edit-mode-helpers | 6.8KB | Admin-only functionality: [viewmode:disabled], [editmode:resource], [editmode:onclick] |
|
|
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() |
|
|
66
66
|
| save-system | 13.4KB | CMD+S, [trigger-save] button, savestatus attribute |
|
|
@@ -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 (~
|
|
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 => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Option Visibility
|
|
2
|
+
* Option Visibility
|
|
3
3
|
*
|
|
4
4
|
* Shows/hides elements based on `option:` and `option-not:` attributes.
|
|
5
5
|
*
|
|
@@ -23,19 +23,19 @@
|
|
|
23
23
|
* </div>
|
|
24
24
|
*
|
|
25
25
|
* HOW IT WORKS:
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Uses a single conditional-hide rule per pattern. Elements get `display: none !important`
|
|
27
|
+
* ONLY when they are NOT inside a matching ancestor scope. When the ancestor condition
|
|
28
|
+
* IS met, the hide rule doesn't match, so the author's original display value
|
|
29
|
+
* (flex, grid, block, etc.) applies naturally — no recovery needed.
|
|
30
30
|
*
|
|
31
31
|
* BROWSER SUPPORT:
|
|
32
|
-
* Requires
|
|
32
|
+
* Requires `:is()` and `:not()` with selector lists (~96% of browsers, 2021+).
|
|
33
33
|
* Falls back gracefully - elements remain visible if unsupported.
|
|
34
34
|
*
|
|
35
35
|
* TRADEOFFS:
|
|
36
36
|
* - Pro: Pure CSS after generation, zero JS overhead for toggling
|
|
37
|
-
* - Pro:
|
|
38
|
-
* -
|
|
37
|
+
* - Pro: No @layer or revert-layer — works with any author CSS (layered or unlayered)
|
|
38
|
+
* - Pro: One rule per pattern instead of two
|
|
39
39
|
* - Con: Pipe character `|` cannot be used as a literal value (reserved as OR delimiter)
|
|
40
40
|
*/
|
|
41
41
|
|
|
@@ -78,16 +78,7 @@ const optionVisibility = {
|
|
|
78
78
|
_unsubscribe: null,
|
|
79
79
|
|
|
80
80
|
log(...args) {
|
|
81
|
-
if (this.debug) console.log('[OptionVisibility
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Check if browser supports the layer approach
|
|
86
|
-
*/
|
|
87
|
-
isSupported() {
|
|
88
|
-
return typeof CSS !== 'undefined'
|
|
89
|
-
&& typeof CSS.supports === 'function'
|
|
90
|
-
&& CSS.supports('display', 'revert-layer');
|
|
81
|
+
if (this.debug) console.log('[OptionVisibility]', ...args);
|
|
91
82
|
},
|
|
92
83
|
|
|
93
84
|
/**
|
|
@@ -126,61 +117,51 @@ const optionVisibility = {
|
|
|
126
117
|
},
|
|
127
118
|
|
|
128
119
|
/**
|
|
129
|
-
* Generate CSS rules
|
|
120
|
+
* Generate CSS rules for conditional hiding.
|
|
121
|
+
*
|
|
122
|
+
* Each pattern produces a single rule that hides the element ONLY when
|
|
123
|
+
* it's NOT inside a matching ancestor scope. When the condition IS met,
|
|
124
|
+
* the rule doesn't match, so the author's original display value applies.
|
|
130
125
|
*/
|
|
131
126
|
generateCSS(patterns) {
|
|
132
127
|
if (!patterns.length) return '';
|
|
133
128
|
|
|
134
|
-
|
|
129
|
+
return patterns.map(({ name, rawValue, values, negated }) => {
|
|
135
130
|
const safeName = CSS.escape(name);
|
|
136
131
|
const safeRawValue = CSS.escape(rawValue);
|
|
137
132
|
const prefix = negated ? 'option-not' : 'option';
|
|
138
133
|
const attrSelector = `[${prefix}\\:${safeName}="${safeRawValue}"]`;
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
const hideRule = `${attrSelector}{display:none!important}`;
|
|
142
|
-
|
|
143
|
-
// Show rule depends on type
|
|
144
|
-
let showRule;
|
|
135
|
+
let scopeSelectors;
|
|
145
136
|
if (negated) {
|
|
146
|
-
// option-not:
|
|
147
|
-
// Uses :not(sel1, sel2) selector list syntax
|
|
137
|
+
// option-not: active when ancestor has attr but NOT any of the values
|
|
148
138
|
const notList = values.map(v => `[${safeName}="${CSS.escape(v)}"]`).join(',');
|
|
149
|
-
|
|
139
|
+
const scope = `[${safeName}]:not(${notList})`;
|
|
140
|
+
scopeSelectors = `${scope},${scope} *`;
|
|
150
141
|
} else {
|
|
151
|
-
// option:
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
showRule = `${showSelectors}{display:revert-layer!important}`;
|
|
142
|
+
// option: active when ancestor has ANY of the values
|
|
143
|
+
const self = values.map(v => `[${safeName}="${CSS.escape(v)}"]`);
|
|
144
|
+
const desc = values.map(v => `[${safeName}="${CSS.escape(v)}"] *`);
|
|
145
|
+
scopeSelectors = [...self, ...desc].join(',');
|
|
156
146
|
}
|
|
157
147
|
|
|
158
|
-
return
|
|
148
|
+
return `${attrSelector}:not(:is(${scopeSelectors})){display:none!important}`;
|
|
159
149
|
}).join('');
|
|
160
|
-
|
|
161
|
-
return `@layer ${STYLE_NAME}{${rules}}`;
|
|
162
150
|
},
|
|
163
151
|
|
|
164
152
|
/**
|
|
165
153
|
* Update the style element with current rules
|
|
166
154
|
*/
|
|
167
155
|
update() {
|
|
168
|
-
if (!this.isSupported()) {
|
|
169
|
-
this.log('Browser lacks revert-layer support, skipping');
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
156
|
try {
|
|
174
157
|
const attributes = this.findOptionAttributes();
|
|
175
158
|
const css = this.generateCSS(attributes);
|
|
176
|
-
// mutations-ignore: This style tag is regenerated on load. Without this,
|
|
177
|
-
// the mutation observer would detect it as a change, delaying the settled signal.
|
|
178
159
|
insertStyles(STYLE_NAME, css, (style) => {
|
|
179
160
|
style.setAttribute('mutations-ignore', '');
|
|
180
161
|
});
|
|
181
162
|
this.log(`Generated ${attributes.length} rules`);
|
|
182
163
|
} catch (error) {
|
|
183
|
-
console.error('[OptionVisibility
|
|
164
|
+
console.error('[OptionVisibility] Error generating rules:', error);
|
|
184
165
|
}
|
|
185
166
|
},
|
|
186
167
|
|
|
@@ -194,11 +175,6 @@ const optionVisibility = {
|
|
|
194
175
|
|
|
195
176
|
this._started = true;
|
|
196
177
|
|
|
197
|
-
if (!this.isSupported()) {
|
|
198
|
-
console.warn('[OptionVisibility:Layer] Browser lacks revert-layer support. Elements will remain visible.');
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
178
|
this.update();
|
|
203
179
|
|
|
204
180
|
// selectorFilter only triggers on option:/option-not: attribute changes (new patterns).
|
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.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
|
},
|