hyperclayjs 1.7.0 → 1.8.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 (60) hide show
  1. package/README.md +13 -13
  2. package/package.json +17 -25
  3. package/src/core/adminContenteditable.js +51 -0
  4. package/{core → src/core}/adminInputs.js +29 -8
  5. package/src/core/adminOnClick.js +54 -0
  6. package/{core → src/core}/adminResources.js +25 -5
  7. package/{core → src/core}/savePage.js +1 -1
  8. package/{core → src/core}/savePageCore.js +12 -2
  9. package/{custom-attributes → src/custom-attributes}/domHelpers.js +17 -4
  10. package/{custom-attributes → src/custom-attributes}/events.js +2 -0
  11. package/src/custom-attributes/onmutation.js +90 -0
  12. package/src/custom-attributes/onpagemutation.js +32 -0
  13. package/{custom-attributes → src/custom-attributes}/sortable.js +16 -1
  14. package/{dom-utilities → src/dom-utilities}/All.js +22 -0
  15. package/{hyperclay.js → src/hyperclay.js} +3 -3
  16. package/{module-dependency-graph.json → src/module-dependency-graph.json} +12 -22
  17. package/{ui → src/ui}/prompts.js +13 -18
  18. package/{ui → src/ui}/theModal.js +101 -0
  19. package/{ui → src/ui}/toast.js +4 -3
  20. package/core/adminContenteditable.js +0 -36
  21. package/core/adminOnClick.js +0 -31
  22. package/custom-attributes/onpagemutation.js +0 -20
  23. /package/{communication → src/communication}/behaviorCollector.js +0 -0
  24. /package/{communication → src/communication}/sendMessage.js +0 -0
  25. /package/{communication → src/communication}/uploadFile.js +0 -0
  26. /package/{core → src/core}/adminSystem.js +0 -0
  27. /package/{core → src/core}/autosave.js +0 -0
  28. /package/{core → src/core}/editmode.js +0 -0
  29. /package/{core → src/core}/editmodeSystem.js +0 -0
  30. /package/{core → src/core}/enablePersistentFormInputValues.js +0 -0
  31. /package/{core → src/core}/exportToWindow.js +0 -0
  32. /package/{core → src/core}/isAdminOfCurrentResource.js +0 -0
  33. /package/{core → src/core}/optionVisibility.js +0 -0
  34. /package/{core → src/core}/saveToast.js +0 -0
  35. /package/{core → src/core}/setPageTypeOnDocumentElement.js +0 -0
  36. /package/{custom-attributes → src/custom-attributes}/ajaxElements.js +0 -0
  37. /package/{custom-attributes → src/custom-attributes}/autosize.js +0 -0
  38. /package/{custom-attributes → src/custom-attributes}/inputHelpers.js +0 -0
  39. /package/{custom-attributes → src/custom-attributes}/onaftersave.js +0 -0
  40. /package/{custom-attributes → src/custom-attributes}/onclickaway.js +0 -0
  41. /package/{custom-attributes → src/custom-attributes}/onclone.js +0 -0
  42. /package/{custom-attributes → src/custom-attributes}/onrender.js +0 -0
  43. /package/{custom-attributes → src/custom-attributes}/preventEnter.js +0 -0
  44. /package/{dom-utilities → src/dom-utilities}/getDataFromForm.js +0 -0
  45. /package/{dom-utilities → src/dom-utilities}/insertStyleTag.js +0 -0
  46. /package/{dom-utilities → src/dom-utilities}/onDomReady.js +0 -0
  47. /package/{dom-utilities → src/dom-utilities}/onLoad.js +0 -0
  48. /package/{string-utilities → src/string-utilities}/copy-to-clipboard.js +0 -0
  49. /package/{string-utilities → src/string-utilities}/query.js +0 -0
  50. /package/{string-utilities → src/string-utilities}/slugify.js +0 -0
  51. /package/{ui → src/ui}/toast-hyperclay.js +0 -0
  52. /package/{utilities → src/utilities}/cookie.js +0 -0
  53. /package/{utilities → src/utilities}/debounce.js +0 -0
  54. /package/{utilities → src/utilities}/loadVendorScript.js +0 -0
  55. /package/{utilities → src/utilities}/mutation.js +0 -0
  56. /package/{utilities → src/utilities}/nearest.js +0 -0
  57. /package/{utilities → src/utilities}/pipe.js +0 -0
  58. /package/{utilities → src/utilities}/throttle.js +0 -0
  59. /package/{vendor → src/vendor}/Sortable.vendor.js +0 -0
  60. /package/{vendor → src/vendor}/idiomorph.min.js +0 -0
package/README.md CHANGED
@@ -21,7 +21,7 @@ Destructure directly from the import:
21
21
 
22
22
  ```html
23
23
  <script type="module">
24
- const { toast, savePage } = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/hyperclay.js?preset=standard');
24
+ const { toast, savePage } = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/src/hyperclay.js?preset=standard');
25
25
  toast('Hello!');
26
26
  </script>
27
27
  ```
@@ -30,7 +30,7 @@ Or with custom features:
30
30
 
31
31
  ```html
32
32
  <script type="module">
33
- const { toast, ask } = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/hyperclay.js?features=toast,dialogs');
33
+ const { toast, ask } = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/src/hyperclay.js?features=toast,dialogs');
34
34
  </script>
35
35
  ```
36
36
 
@@ -59,10 +59,10 @@ import 'hyperclayjs/presets/standard.js';
59
59
  |--------|------|-------------|
60
60
  | autosave | 1.1KB | Auto-save on DOM changes, unsaved changes warning |
61
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] |
62
+ | edit-mode-helpers | 7.5KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
63
63
  | option-visibility | 5.9KB | Dynamic show/hide based on ancestor state with option:attribute="value" |
64
64
  | persist | 2.5KB | Persist input/select/textarea values to the DOM with [persist] attribute |
65
- | save-core | 6.3KB | Basic save function only - hyperclay.savePage() |
65
+ | save-core | 6.5KB | Basic save function only - hyperclay.savePage() |
66
66
  | save-system | 7.1KB | Manual save: keyboard shortcut (CMD+S), save button, change tracking |
67
67
  | save-toast | 0.9KB | Toast notifications for save events |
68
68
 
@@ -71,18 +71,18 @@ import 'hyperclayjs/presets/standard.js';
71
71
  | Module | Size | Description |
72
72
  |--------|------|-------------|
73
73
  | ajax-elements | 2.8KB | [ajax-form], [ajax-button] for async form submissions |
74
- | dom-helpers | 5.7KB | el.nearest, el.val, el.text, el.exec, el.cycle |
75
- | event-attrs | 3.6KB | [onclickaway], [onclone], [onpagemutation], [onrender] |
74
+ | dom-helpers | 6.2KB | el.nearest, el.val, el.text, el.exec, el.cycle |
75
+ | event-attrs | 4.1KB | [onclickaway], [onclone], [onpagemutation], [onrender] |
76
76
  | input-helpers | 1.2KB | [prevent-enter], [autosize] for textareas |
77
77
  | onaftersave | 1.2KB | [onaftersave] attribute - run JS when save status changes |
78
- | sortable | 2.8KB | Drag-drop sorting with [sortable], lazy-loads ~118KB Sortable.js in edit mode |
78
+ | sortable | 3.4KB | Drag-drop sorting with [sortable], lazy-loads ~118KB Sortable.js in edit mode |
79
79
 
80
80
  ### UI Components (User interface elements)
81
81
 
82
82
  | Module | Size | Description |
83
83
  |--------|------|-------------|
84
- | dialogs | 8.4KB | ask(), consent(), tell(), snippet() dialog functions |
85
- | the-modal | 19.8KB | Full modal window creation system - window.theModal |
84
+ | dialogs | 7.7KB | ask(), consent(), tell(), snippet() dialog functions |
85
+ | the-modal | 21.8KB | Full modal window creation system - window.theModal |
86
86
  | toast | 7.7KB | Success/error message notifications, toast(msg, msgType) |
87
87
 
88
88
  ### Utilities (Core utilities (often auto-included))
@@ -99,7 +99,7 @@ import 'hyperclayjs/presets/standard.js';
99
99
 
100
100
  | Module | Size | Description |
101
101
  |--------|------|-------------|
102
- | all-js | 14KB | Full DOM manipulation library |
102
+ | all-js | 14.4KB | Full DOM manipulation library |
103
103
  | dom-ready | 0.4KB | DOM ready callback |
104
104
  | form-data | 2KB | Extract form data as an object |
105
105
  | style-injection | 1.1KB | Dynamic stylesheet injection |
@@ -127,17 +127,17 @@ import 'hyperclayjs/presets/standard.js';
127
127
 
128
128
  ## Presets
129
129
 
130
- ### Minimal (~27.8KB)
130
+ ### Minimal (~30.1KB)
131
131
  Essential features for basic editing
132
132
 
133
133
  **Modules:** `save-core`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`
134
134
 
135
- ### Standard (~45.5KB)
135
+ ### Standard (~48.8KB)
136
136
  Standard feature set for most use cases
137
137
 
138
138
  **Modules:** `save-core`, `save-system`, `edit-mode-helpers`, `persist`, `option-visibility`, `event-attrs`, `dom-helpers`, `toast`, `save-toast`, `export-to-window`
139
139
 
140
- ### Everything (~150.2KB)
140
+ ### Everything (~155.8KB)
141
141
  All available features
142
142
 
143
143
  Includes all available modules across all categories.
package/package.json CHANGED
@@ -1,39 +1,31 @@
1
1
  {
2
2
  "name": "hyperclayjs",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Modular JavaScript library for building interactive HTML applications with Hyperclay",
5
5
  "type": "module",
6
- "main": "hyperclay.js",
7
- "module": "hyperclay.js",
6
+ "main": "src/hyperclay.js",
7
+ "module": "src/hyperclay.js",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./hyperclay.js",
11
- "default": "./hyperclay.js"
10
+ "import": "./src/hyperclay.js",
11
+ "default": "./src/hyperclay.js"
12
12
  },
13
- "./core/*": "./core/*.js",
14
- "./custom-attributes/*": "./custom-attributes/*.js",
15
- "./ui/*": "./ui/*.js",
16
- "./utilities/*": "./utilities/*.js",
17
- "./dom-utilities/*": "./dom-utilities/*.js",
18
- "./string-utilities/*": "./string-utilities/*.js",
19
- "./communication/*": "./communication/*.js",
20
- "./vendor/*": "./vendor/*.js"
13
+ "./core/*": "./src/core/*.js",
14
+ "./custom-attributes/*": "./src/custom-attributes/*.js",
15
+ "./ui/*": "./src/ui/*.js",
16
+ "./utilities/*": "./src/utilities/*.js",
17
+ "./dom-utilities/*": "./src/dom-utilities/*.js",
18
+ "./string-utilities/*": "./src/string-utilities/*.js",
19
+ "./communication/*": "./src/communication/*.js",
20
+ "./vendor/*": "./src/vendor/*.js"
21
21
  },
22
22
  "files": [
23
- "core",
24
- "custom-attributes",
25
- "ui",
26
- "utilities",
27
- "dom-utilities",
28
- "string-utilities",
29
- "communication",
30
- "vendor",
31
- "hyperclay.js",
32
- "module-dependency-graph.json"
23
+ "src"
33
24
  ],
34
25
  "scripts": {
35
- "dev": "npm run build && http-server -p 3535 -c-1 -o /index.html",
26
+ "dev": "npm run build && npm run build:website && http-server website -p 3535 -c-1 -o /index.html",
36
27
  "build": "npm run generate:deps && npm run build:loader && npm run build:readme && npm run build:load-jsdelivr && npm run build:index-url",
28
+ "build:website": "node scripts/build-website.js",
37
29
  "generate:deps": "node build/generate-dependency-graph.js",
38
30
  "build:loader": "node build/build-loader.js",
39
31
  "build:readme": "node build/generate-readme.js",
@@ -44,7 +36,7 @@
44
36
  "format": "prettier --write .",
45
37
  "release": "./scripts/release.sh",
46
38
  "prepublishOnly": "npm run build && npm test",
47
- "postpublish": "test -n \"$SKIP_POSTPUBLISH\" || open http://127.0.0.1:3535/build/load-jsdelivr.html"
39
+ "postpublish": "test -n \"$SKIP_POSTPUBLISH\" || open http://127.0.0.1:3535/load-jsdelivr.html"
48
40
  },
49
41
  "repository": {
50
42
  "type": "git",
@@ -0,0 +1,51 @@
1
+ import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
2
+ import onDomReady from "../dom-utilities/onDomReady.js";
3
+ import {beforeSave} from "./savePage.js";
4
+
5
+ export function disableContentEditableBeforeSave () {
6
+ beforeSave(docElem => {
7
+ docElem.querySelectorAll('[edit-mode-contenteditable]').forEach(resource => {
8
+ const originalValue = resource.getAttribute("contenteditable");
9
+ resource.setAttribute("inert-contenteditable", originalValue);
10
+ resource.removeAttribute("contenteditable");
11
+ });
12
+ });
13
+ }
14
+
15
+ export function enableContentEditableForAdminOnPageLoad () {
16
+ if (!isEditMode) return;
17
+
18
+ onDomReady(() => {
19
+ enableContentEditable();
20
+ });
21
+ }
22
+
23
+ // Runtime toggle functions
24
+ export function enableContentEditable() {
25
+ document.querySelectorAll('[edit-mode-contenteditable]').forEach(el => {
26
+ let val = el.getAttribute("inert-contenteditable");
27
+ if (!["false", "plaintext-only"].includes(val)) val = "true";
28
+ el.setAttribute("contenteditable", val);
29
+ el.removeAttribute("inert-contenteditable");
30
+ });
31
+ }
32
+
33
+ export function disableContentEditable() {
34
+ document.querySelectorAll('[edit-mode-contenteditable]').forEach(el => {
35
+ const val = el.getAttribute("contenteditable") || "true";
36
+ el.setAttribute("inert-contenteditable", val);
37
+ el.removeAttribute("contenteditable");
38
+ });
39
+ }
40
+
41
+ // Auto-initialize
42
+ export function init() {
43
+ disableContentEditableBeforeSave();
44
+ enableContentEditableForAdminOnPageLoad();
45
+ }
46
+
47
+ // Export to window
48
+ window.hyperclay = window.hyperclay || {};
49
+ window.hyperclay.enableContentEditable = enableContentEditable;
50
+ window.hyperclay.disableContentEditable = disableContentEditable;
51
+ window.h = window.hyperclay;
@@ -18,13 +18,28 @@ export function enableAdminInputsOnPageLoad() {
18
18
  if (!isEditMode) return;
19
19
 
20
20
  onDomReady(() => {
21
- document.querySelectorAll('[edit-mode-input]').forEach(input => {
22
- if (supportsReadonly(input)) {
23
- input.removeAttribute('readonly');
24
- } else {
25
- input.removeAttribute('disabled');
26
- }
27
- });
21
+ enableAdminInputs();
22
+ });
23
+ }
24
+
25
+ // Runtime toggle functions
26
+ export function enableAdminInputs() {
27
+ document.querySelectorAll('[edit-mode-input]').forEach(input => {
28
+ if (supportsReadonly(input)) {
29
+ input.removeAttribute('readonly');
30
+ } else {
31
+ input.removeAttribute('disabled');
32
+ }
33
+ });
34
+ }
35
+
36
+ export function disableAdminInputs() {
37
+ document.querySelectorAll('[edit-mode-input]').forEach(input => {
38
+ if (supportsReadonly(input)) {
39
+ input.setAttribute('readonly', '');
40
+ } else {
41
+ input.setAttribute('disabled', '');
42
+ }
28
43
  });
29
44
  }
30
45
 
@@ -55,4 +70,10 @@ function supportsReadonly(element) {
55
70
  export function init() {
56
71
  disableAdminInputsBeforeSave();
57
72
  enableAdminInputsOnPageLoad();
58
- }
73
+ }
74
+
75
+ // Export to window
76
+ window.hyperclay = window.hyperclay || {};
77
+ window.hyperclay.enableAdminInputs = enableAdminInputs;
78
+ window.hyperclay.disableAdminInputs = disableAdminInputs;
79
+ window.h = window.hyperclay;
@@ -0,0 +1,54 @@
1
+ import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
2
+ import onDomReady from "../dom-utilities/onDomReady.js";
3
+ import {beforeSave} from "./savePage.js";
4
+
5
+ export function disableOnClickBeforeSave () {
6
+ beforeSave(docElem => {
7
+ docElem.querySelectorAll('[edit-mode-onclick]').forEach(resource => {
8
+ const originalValue = resource.getAttribute("onclick");
9
+ resource.setAttribute("inert-onclick", originalValue);
10
+ resource.removeAttribute("onclick");
11
+ });
12
+ });
13
+ }
14
+
15
+ export function enableOnClickForAdminOnPageLoad () {
16
+ if (!isEditMode) return;
17
+
18
+ onDomReady(() => {
19
+ enableOnClick();
20
+ });
21
+ }
22
+
23
+ // Runtime toggle functions
24
+ export function enableOnClick() {
25
+ document.querySelectorAll('[edit-mode-onclick]').forEach(el => {
26
+ const val = el.getAttribute("inert-onclick");
27
+ if (val) {
28
+ el.setAttribute("onclick", val);
29
+ el.removeAttribute("inert-onclick");
30
+ }
31
+ });
32
+ }
33
+
34
+ export function disableOnClick() {
35
+ document.querySelectorAll('[edit-mode-onclick]').forEach(el => {
36
+ const val = el.getAttribute("onclick");
37
+ if (val) {
38
+ el.setAttribute("inert-onclick", val);
39
+ el.removeAttribute("onclick");
40
+ }
41
+ });
42
+ }
43
+
44
+ // Auto-initialize
45
+ export function init() {
46
+ disableOnClickBeforeSave();
47
+ enableOnClickForAdminOnPageLoad();
48
+ }
49
+
50
+ // Export to window
51
+ window.hyperclay = window.hyperclay || {};
52
+ window.hyperclay.enableOnClick = enableOnClick;
53
+ window.hyperclay.disableOnClick = disableOnClick;
54
+ window.h = window.hyperclay;
@@ -18,11 +18,25 @@ export function enableAdminResourcesOnPageLoad () {
18
18
  if (!isEditMode) return;
19
19
 
20
20
  onDomReady(() => {
21
- document.querySelectorAll('[edit-mode-resource]:is(style, link, script)[type^="inert/"]').forEach(resource => {
22
- // works for js and css
23
- resource.type = resource.type.replace(/inert\//g, '');
21
+ enableAdminResources();
22
+ });
23
+ }
24
+
25
+ // Runtime toggle functions
26
+ export function enableAdminResources() {
27
+ document.querySelectorAll('[edit-mode-resource]:is(style, link, script)[type^="inert/"]').forEach(resource => {
28
+ resource.type = resource.type.replace(/inert\//g, '');
29
+ resource.replaceWith(resource.cloneNode(true));
30
+ });
31
+ }
32
+
33
+ export function disableAdminResources() {
34
+ document.querySelectorAll('[edit-mode-resource]:is(style, link, script)').forEach(resource => {
35
+ const currentType = resource.getAttribute('type') || 'text/javascript';
36
+ if (!currentType.startsWith('inert/')) {
37
+ resource.setAttribute('type', `inert/${currentType}`);
24
38
  resource.replaceWith(resource.cloneNode(true));
25
- });
39
+ }
26
40
  });
27
41
  }
28
42
 
@@ -30,4 +44,10 @@ export function enableAdminResourcesOnPageLoad () {
30
44
  export function init() {
31
45
  disableAdminResourcesBeforeSave();
32
46
  enableAdminResourcesOnPageLoad();
33
- }
47
+ }
48
+
49
+ // Export to window
50
+ window.hyperclay = window.hyperclay || {};
51
+ window.hyperclay.enableAdminResources = enableAdminResources;
52
+ window.hyperclay.disableAdminResources = disableAdminResources;
53
+ window.h = window.hyperclay;
@@ -111,7 +111,7 @@ document.addEventListener('DOMContentLoaded', () => {
111
111
  * @param {Function} callback - Optional callback for custom handling
112
112
  */
113
113
  export function savePage(callback = () => {}) {
114
- if (!isEditMode) {
114
+ if (!isEditMode && !window.hyperclay?.testMode) {
115
115
  return;
116
116
  }
117
117
 
@@ -73,11 +73,21 @@ export function getPageContents() {
73
73
  * });
74
74
  */
75
75
  export function savePage(callback = () => {}) {
76
- if (!isEditMode || saveInProgress) {
76
+ if (saveInProgress) {
77
+ return;
78
+ }
79
+ if (!isEditMode && !window.hyperclay?.testMode) {
77
80
  return;
78
81
  }
79
82
 
80
- const currentContents = getPageContents();
83
+ let currentContents;
84
+ try {
85
+ currentContents = getPageContents();
86
+ } catch (err) {
87
+ console.error('savePage: getPageContents failed', err);
88
+ callback({msg: err.message, msgType: "error"});
89
+ return;
90
+ }
81
91
  saveInProgress = true;
82
92
 
83
93
  // Test mode: skip network request, return mock success
@@ -24,22 +24,35 @@ function init () {
24
24
  }
25
25
  });
26
26
 
27
- // elem.val.project returns the value of the nearest "project" attribute
28
- // elem.val.project = "hello world" sets the value of the nearest "project" attribute
27
+ // elem.val.project returns the value of the nearest element with "project" attribute
28
+ // elem.val.project = "hello world" sets the value of the nearest element with "project" attribute
29
+ // For form elements (input/select/textarea), uses the value property; otherwise uses the attribute
29
30
  Object.defineProperty(HTMLElement.prototype, 'val', {
30
31
  configurable: true,
31
32
  get: function() {
32
33
  let element = this;
33
34
 
35
+ const isFormElement = (elem) =>
36
+ elem.tagName === 'INPUT' || elem.tagName === 'SELECT' || elem.tagName === 'TEXTAREA';
37
+
34
38
  const handler = {
35
39
  get(target, prop) {
36
- return nearest(element, `[${prop}], .${prop}`, elem => elem.getAttribute(prop));
40
+ return nearest(element, `[${prop}], .${prop}`, elem => {
41
+ if (isFormElement(elem)) {
42
+ return elem.value;
43
+ }
44
+ return elem.getAttribute(prop);
45
+ });
37
46
  },
38
47
  set(target, prop, value) {
39
48
  const foundElem = nearest(element, `[${prop}], .${prop}`);
40
49
 
41
50
  if (foundElem) {
42
- foundElem.setAttribute(prop, value);
51
+ if (isFormElement(foundElem)) {
52
+ foundElem.value = value;
53
+ } else {
54
+ foundElem.setAttribute(prop, value);
55
+ }
43
56
  }
44
57
 
45
58
  return true;
@@ -1,12 +1,14 @@
1
1
  // Events module - combines all event attribute handlers
2
2
  import { init as initOnclickaway } from './onclickaway.js';
3
3
  import { init as initOnclone } from './onclone.js';
4
+ import { init as initOnmutation } from './onmutation.js';
4
5
  import { init as initOnpagemutation } from './onpagemutation.js';
5
6
  import { init as initOnrender } from './onrender.js';
6
7
 
7
8
  function init() {
8
9
  initOnclickaway();
9
10
  initOnclone();
11
+ initOnmutation();
10
12
  initOnpagemutation();
11
13
  initOnrender();
12
14
  }
@@ -0,0 +1,90 @@
1
+ /*
2
+ [onmutation] - Trigger code when this element or its children change
3
+
4
+ Usage:
5
+ <div onmutation="console.log('My content changed')">
6
+ <span contenteditable>Edit me</span>
7
+ </div>
8
+
9
+ Unlike [onglobalmutation]/[onpagemutation] which fires on ANY DOM change,
10
+ [onmutation] only fires when the element itself or its descendants mutate.
11
+ */
12
+ import Mutation from "../utilities/mutation.js";
13
+
14
+ const observers = new WeakMap();
15
+
16
+ function setupMutationObserver(element) {
17
+ if (observers.has(element)) return;
18
+
19
+ const executeMutation = async () => {
20
+ try {
21
+ const code = element.getAttribute('onmutation');
22
+ if (!code) return;
23
+ const asyncFn = new Function(`return (async function() { ${code} })`)();
24
+ await asyncFn.call(element);
25
+ } catch (error) {
26
+ console.error('Error in onmutation execution:', error);
27
+ }
28
+ };
29
+
30
+ const observer = new MutationObserver(() => {
31
+ executeMutation();
32
+ });
33
+
34
+ observer.observe(element, {
35
+ childList: true,
36
+ subtree: true,
37
+ characterData: true,
38
+ attributes: true
39
+ });
40
+
41
+ observers.set(element, observer);
42
+ }
43
+
44
+ function teardownMutationObserver(element) {
45
+ const observer = observers.get(element);
46
+ if (observer) {
47
+ observer.disconnect();
48
+ observers.delete(element);
49
+ }
50
+ }
51
+
52
+ function init() {
53
+ // Set up existing elements
54
+ document.querySelectorAll('[onmutation]').forEach(setupMutationObserver);
55
+
56
+ // Watch for dynamically added elements with onmutation
57
+ Mutation.onAddElement({
58
+ selectorFilter: '[onmutation]',
59
+ debounce: 200
60
+ }, (changes) => {
61
+ changes.forEach(({ element }) => {
62
+ setupMutationObserver(element);
63
+ });
64
+ });
65
+
66
+ // Clean up when elements are removed
67
+ Mutation.onRemoveElement({
68
+ selectorFilter: '[onmutation]',
69
+ debounce: 200
70
+ }, (changes) => {
71
+ changes.forEach(({ element }) => {
72
+ teardownMutationObserver(element);
73
+ });
74
+ });
75
+
76
+ // Watch for attribute removal
77
+ Mutation.onAttribute({
78
+ selectorFilter: '[onmutation]',
79
+ debounce: 200
80
+ }, (changes) => {
81
+ changes.forEach(({ element, attribute, newValue }) => {
82
+ if (attribute === 'onmutation' && newValue === null) {
83
+ teardownMutationObserver(element);
84
+ }
85
+ });
86
+ });
87
+ }
88
+
89
+ export { init };
90
+ export default init;
@@ -0,0 +1,32 @@
1
+ /*
2
+ [onpagemutation] / [onglobalmutation] - Trigger code when ANY element on the page changes
3
+
4
+ Usage:
5
+ <span onglobalmutation="this.textContent = All('li').length">0</span>
6
+ <span onpagemutation="this.textContent = All('li').length">0</span>
7
+
8
+ Both attributes are equivalent - onglobalmutation is the preferred name for clarity.
9
+ */
10
+ import Mutation from "../utilities/mutation.js";
11
+
12
+ function init() {
13
+ const executeGlobalMutation = async element => {
14
+ try {
15
+ // Support both onglobalmutation and onpagemutation (legacy)
16
+ const code = element.getAttribute('onglobalmutation') || element.getAttribute('onpagemutation');
17
+ const asyncFn = new Function(`return (async function() { ${code} })`)();
18
+ await asyncFn.call(element);
19
+ } catch (error) {
20
+ console.error('Error in onglobalmutation/onpagemutation execution:', error);
21
+ }
22
+ };
23
+
24
+ Mutation.onAnyChange({
25
+ debounce: 200,
26
+ omitChangeDetails: true
27
+ }, () => {
28
+ document.querySelectorAll('[onglobalmutation], [onpagemutation]').forEach(executeGlobalMutation);
29
+ });
30
+ }
31
+ export { init };
32
+ export default init;
@@ -5,6 +5,8 @@
5
5
  How to use:
6
6
  - add `sortable` attribute to an element to make children sortable
7
7
  - e.g. <div sortable></div>
8
+ - add `onsorting` attribute to execute code during drag
9
+ - e.g. <ul sortable onsorting="console.log('Dragging!')"></ul>
8
10
  - add `onsorted` attribute to execute code when items are sorted
9
11
  - e.g. <ul sortable onsorted="console.log('Items reordered!')"></ul>
10
12
 
@@ -39,7 +41,20 @@ function makeSortable(sortableElem, Sortable) {
39
41
  options.handle = '[sortable-handle]';
40
42
  }
41
43
 
42
- // Add onsorted callback if attribute exists
44
+ // Add onsorting callback if attribute exists (fires during drag)
45
+ const onsortingCode = sortableElem.getAttribute('onsorting');
46
+ if (onsortingCode) {
47
+ options.onMove = function(evt) {
48
+ try {
49
+ const asyncFn = new Function(`return (async function(evt) { ${onsortingCode} })`)();
50
+ asyncFn.call(sortableElem, evt);
51
+ } catch (error) {
52
+ console.error('Error in onsorting execution:', error);
53
+ }
54
+ };
55
+ }
56
+
57
+ // Add onsorted callback if attribute exists (fires after drop)
43
58
  const onsortedCode = sortableElem.getAttribute('onsorted');
44
59
  if (onsortedCode) {
45
60
  options.onEnd = function(evt) {
@@ -336,6 +336,28 @@ const defaultPlugins = {
336
336
  });
337
337
 
338
338
  return this;
339
+ },
340
+
341
+ pluck(attr) {
342
+ return this.map(el => el.getAttribute(attr));
343
+ },
344
+
345
+ unique() {
346
+ return [...new Set(this)];
347
+ },
348
+
349
+ sortBy(fn) {
350
+ if (typeof fn === 'string') {
351
+ const attr = fn;
352
+ fn = el => el.getAttribute(attr);
353
+ }
354
+ return [...this].sort((a, b) => {
355
+ const aVal = fn(a);
356
+ const bVal = fn(b);
357
+ if (aVal < bVal) return -1;
358
+ if (aVal > bVal) return 1;
359
+ return 0;
360
+ });
339
361
  }
340
362
  }
341
363
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperclayJS v1.7.0 - Minimal Browser-Native Loader
2
+ * HyperclayJS v1.8.0 - Minimal Browser-Native Loader
3
3
  *
4
4
  * Modules auto-init when imported (no separate init call needed).
5
5
  * Include `export-to-window` feature to export to window.hyperclay.
@@ -8,11 +8,11 @@
8
8
  *
9
9
  * <script type="module">
10
10
  * // With window.hyperclay (default presets include export-to-window):
11
- * await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/hyperclay.js?preset=minimal');
11
+ * await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/src/hyperclay.js?preset=minimal');
12
12
  * const { toast, savePage } = window.hyperclay;
13
13
  *
14
14
  * // ES modules only (exclude export-to-window):
15
- * const hyperclay = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/hyperclay.js?preset=minimal&exclude=export-to-window');
15
+ * const hyperclay = await import('https://cdn.jsdelivr.net/npm/hyperclayjs@1/src/hyperclay.js?preset=minimal&exclude=export-to-window');
16
16
  * const modules = window.hyperclayModules;
17
17
  * </script>
18
18
  *
@@ -1,15 +1,5 @@
1
1
  {
2
2
  "rawDependencies": {
3
- "__tests__/All.test.js": [
4
- "dom-utilities/All.js"
5
- ],
6
- "babel.config.js": [],
7
- "build/build-loader.js": [],
8
- "build/generate-dependency-graph.js": [],
9
- "build/generate-load-jsdelivr.js": [],
10
- "build/generate-readme.js": [],
11
- "build/hyperclay.template.js": [],
12
- "build/update-index-url.js": [],
13
3
  "communication/behaviorCollector.js": [],
14
4
  "communication/sendMessage.js": [
15
5
  "communication/behaviorCollector.js",
@@ -89,9 +79,6 @@
89
79
  "core/savePage.js",
90
80
  "dom-utilities/onDomReady.js"
91
81
  ],
92
- "coverage/lcov-report/block-navigation.js": [],
93
- "coverage/lcov-report/prettify.js": [],
94
- "coverage/lcov-report/sorter.js": [],
95
82
  "custom-attributes/ajaxElements.js": [
96
83
  "dom-utilities/getDataFromForm.js"
97
84
  ],
@@ -103,6 +90,7 @@
103
90
  "custom-attributes/events.js": [
104
91
  "custom-attributes/onclickaway.js",
105
92
  "custom-attributes/onclone.js",
93
+ "custom-attributes/onmutation.js",
106
94
  "custom-attributes/onpagemutation.js",
107
95
  "custom-attributes/onrender.js"
108
96
  ],
@@ -113,6 +101,9 @@
113
101
  "custom-attributes/onaftersave.js": [],
114
102
  "custom-attributes/onclickaway.js": [],
115
103
  "custom-attributes/onclone.js": [],
104
+ "custom-attributes/onmutation.js": [
105
+ "utilities/mutation.js"
106
+ ],
116
107
  "custom-attributes/onpagemutation.js": [
117
108
  "utilities/mutation.js"
118
109
  ],
@@ -132,7 +123,6 @@
132
123
  "dom-utilities/onDomReady.js": [],
133
124
  "dom-utilities/onLoad.js": [],
134
125
  "hyperclay.js": [],
135
- "jest.config.js": [],
136
126
  "string-utilities/copy-to-clipboard.js": [],
137
127
  "string-utilities/query.js": [],
138
128
  "string-utilities/slugify.js": [],
@@ -161,7 +151,7 @@
161
151
  "save-core": {
162
152
  "name": "save-core",
163
153
  "category": "core",
164
- "size": 6.3,
154
+ "size": 6.5,
165
155
  "files": [
166
156
  "core/savePageCore.js"
167
157
  ],
@@ -218,7 +208,7 @@
218
208
  "edit-mode-helpers": {
219
209
  "name": "edit-mode-helpers",
220
210
  "category": "core",
221
- "size": 5.4,
211
+ "size": 7.500000000000001,
222
212
  "files": [
223
213
  "core/adminSystem.js",
224
214
  "core/adminContenteditable.js",
@@ -274,7 +264,7 @@
274
264
  "event-attrs": {
275
265
  "name": "event-attrs",
276
266
  "category": "custom-attributes",
277
- "size": 3.6000000000000005,
267
+ "size": 4.1000000000000005,
278
268
  "files": [
279
269
  "custom-attributes/events.js",
280
270
  "custom-attributes/onclickaway.js",
@@ -298,7 +288,7 @@
298
288
  "sortable": {
299
289
  "name": "sortable",
300
290
  "category": "custom-attributes",
301
- "size": 2.8,
291
+ "size": 3.4,
302
292
  "files": [
303
293
  "custom-attributes/sortable.js"
304
294
  ],
@@ -308,7 +298,7 @@
308
298
  "dom-helpers": {
309
299
  "name": "dom-helpers",
310
300
  "category": "custom-attributes",
311
- "size": 5.7,
301
+ "size": 6.2,
312
302
  "files": [
313
303
  "custom-attributes/domHelpers.js"
314
304
  ],
@@ -340,7 +330,7 @@
340
330
  "dialogs": {
341
331
  "name": "dialogs",
342
332
  "category": "ui",
343
- "size": 8.4,
333
+ "size": 7.7,
344
334
  "files": [
345
335
  "ui/prompts.js"
346
336
  ],
@@ -396,7 +386,7 @@
396
386
  "the-modal": {
397
387
  "name": "the-modal",
398
388
  "category": "ui",
399
- "size": 19.8,
389
+ "size": 21.8,
400
390
  "files": [
401
391
  "ui/theModal.js"
402
392
  ],
@@ -512,7 +502,7 @@
512
502
  "all-js": {
513
503
  "name": "all-js",
514
504
  "category": "dom-utilities",
515
- "size": 14,
505
+ "size": 14.4,
516
506
  "files": [
517
507
  "dom-utilities/All.js"
518
508
  ],
@@ -3,7 +3,7 @@ import onDomReady from "../dom-utilities/onDomReady.js";
3
3
  import toast from "./toast.js";
4
4
  import copyToClipboard from "../string-utilities/copy-to-clipboard.js";
5
5
 
6
- const CLOSE_BUTTON_SVG = `<svg class="group" viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"><path class="fill-[#1D2032] group-hover:fill-[#212543]" d="M132 132.5 1 1.5h131v131Z" /><path fill-rule="evenodd" clip-rule="evenodd" d="M0 0h3v1.5h1.5V3H6v1.5h1.5V6H9v1.5h1.5V9H12v1.5h1.5V12H15v1.5h1.5V15H18v1.5h1.5V18H21v1.5h1.5V21H24v1.5h1.5V24H27v1.5h1.5V27H30v1.5h1.5V30H33v1.5h1.5V33H36v1.5h1.5V36H39v1.5h1.5V39H42v1.5h1.5V42H45v1.5h1.5V45H48v1.5h1.5V48H51v1.5h1.5V51H54v1.5h1.5V54H57v1.5h1.5V57H60v1.5h1.5V60H63v1.5h1.5V63H66v1.5h1.5V66H69v1.5h1.5V69H72v1.5h1.5V72H75v1.5h1.5V75H78v1.5h1.5V78H81v1.5h1.5V81H84v1.5h1.5V84H87v1.5h1.5V87H90v1.5h1.5V90H93v1.5h1.5V93H96v1.5h1.5V96H99v1.5h1.5V99h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v3h-3V132H129v-1.5h-1.5V129H126v-1.5h-1.5V126H123v-1.5h-1.5V123H120v-1.5h-1.5V120H117v-1.5h-1.5V117H114v-1.5h-1.5V114H111v-1.5h-1.5V111H108v-1.5h-1.5V108H105v-1.5h-1.5V105H102v-1.5h-1.5V102H99v-1.5h-1.5V99H96v-1.5h-1.5V96H93v-1.5h-1.5V93H90v-1.5h-1.5V90H87v-1.5h-1.5V87H84v-1.5h-1.5V84H81v-1.5h-1.5V81H78v-1.5h-1.5V78H75v-1.5h-1.5V75H72v-1.5h-1.5V72H69v-1.5h-1.5V69H66v-1.5h-1.5V66H63v-1.5h-1.5V63H60v-1.5h-1.5V60H57v-1.5h-1.5V57H54v-1.5h-1.5V54H51v-1.5h-1.5V51H48v-1.5h-1.5V48H45v-1.5h-1.5V45H42v-1.5h-1.5V42H39v-1.5h-1.5V39H36v-1.5h-1.5V36H33v-1.5h-1.5V33H30v-1.5h-1.5V30H27v-1.5h-1.5V27H24v-1.5h-1.5V24H21v-1.5h-1.5V21H18v-1.5h-1.5V18H15v-1.5h-1.5V15H12v-1.5h-1.5V12H9v-1.5H7.5V9H6V7.5H4.5V6H3V4.5H1.5V3H0V0ZM108.8 22h5.2v5.1h-2.6v2.6H109v2.6h-2.6v2.6h-2.6v2.5h-2.6v5.2h2.6V45h2.6v2.6h2.6v2.6h2.5v2.6h2.6V58h-5.1v-2.6h-2.6V53h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6h-5.2v2.6H91v2.6h-2.6v2.6h-2.6v2.5h-2.6V58H78v-5.1h2.6v-2.6H83v-2.6h2.6v-2.6h2.6v-2.5h2.6v-5.2h-2.6V35h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6H78V22h5.2v2.6h2.5V27h2.6v2.6h2.6v2.6h2.5v2.6h5.2v-2.6h2.5v-2.6h2.6v-2.6h2.6v-2.5h2.5V22Z" fill="#fff"/></svg>`;
6
+ const CLOSE_BUTTON_SVG = `<svg viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"><path class="micromodal__close-bg" d="M132 132.5 1 1.5h131v131Z" /><path class="micromodal__close-x" fill-rule="evenodd" clip-rule="evenodd" d="M0 0h3v1.5h1.5V3H6v1.5h1.5V6H9v1.5h1.5V9H12v1.5h1.5V12H15v1.5h1.5V15H18v1.5h1.5V18H21v1.5h1.5V21H24v1.5h1.5V24H27v1.5h1.5V27H30v1.5h1.5V30H33v1.5h1.5V33H36v1.5h1.5V36H39v1.5h1.5V39H42v1.5h1.5V42H45v1.5h1.5V45H48v1.5h1.5V48H51v1.5h1.5V51H54v1.5h1.5V54H57v1.5h1.5V57H60v1.5h1.5V60H63v1.5h1.5V63H66v1.5h1.5V66H69v1.5h1.5V69H72v1.5h1.5V72H75v1.5h1.5V75H78v1.5h1.5V78H81v1.5h1.5V81H84v1.5h1.5V84H87v1.5h1.5V87H90v1.5h1.5V90H93v1.5h1.5V93H96v1.5h1.5V96H99v1.5h1.5V99h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v3h-3V132H129v-1.5h-1.5V129H126v-1.5h-1.5V126H123v-1.5h-1.5V123H120v-1.5h-1.5V120H117v-1.5h-1.5V117H114v-1.5h-1.5V114H111v-1.5h-1.5V111H108v-1.5h-1.5V108H105v-1.5h-1.5V105H102v-1.5h-1.5V102H99v-1.5h-1.5V99H96v-1.5h-1.5V96H93v-1.5h-1.5V93H90v-1.5h-1.5V90H87v-1.5h-1.5V87H84v-1.5h-1.5V84H81v-1.5h-1.5V81H78v-1.5h-1.5V78H75v-1.5h-1.5V75H72v-1.5h-1.5V72H69v-1.5h-1.5V69H66v-1.5h-1.5V66H63v-1.5h-1.5V63H60v-1.5h-1.5V60H57v-1.5h-1.5V57H54v-1.5h-1.5V54H51v-1.5h-1.5V51H48v-1.5h-1.5V48H45v-1.5h-1.5V45H42v-1.5h-1.5V42H39v-1.5h-1.5V39H36v-1.5h-1.5V36H33v-1.5h-1.5V33H30v-1.5h-1.5V30H27v-1.5h-1.5V27H24v-1.5h-1.5V24H21v-1.5h-1.5V21H18v-1.5h-1.5V18H15v-1.5h-1.5V15H12v-1.5h-1.5V12H9v-1.5H7.5V9H6V7.5H4.5V6H3V4.5H1.5V3H0V0ZM108.8 22h5.2v5.1h-2.6v2.6H109v2.6h-2.6v2.6h-2.6v2.5h-2.6v5.2h2.6V45h2.6v2.6h2.6v2.6h2.5v2.6h2.6V58h-5.1v-2.6h-2.6V53h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6h-5.2v2.6H91v2.6h-2.6v2.6h-2.6v2.5h-2.6V58H78v-5.1h2.6v-2.6H83v-2.6h2.6v-2.6h2.6v-2.5h2.6v-5.2h-2.6V35h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6H78V22h5.2v2.6h2.5V27h2.6v2.6h2.6v2.6h2.5v2.6h5.2v-2.6h2.5v-2.6h2.6v-2.6h2.6v-2.5h2.5V22Z" /></svg>`;
7
7
  const CONFIRM_BUTTON_SVG = `<div style="width: 28px;"><svg viewBox="0 0 60 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M34.5 0.75H43.5V5.25H49V9.75H54.5V14.25H60V18.75H54.5V23.25H49V27.75H43.5V32.25H34.5V27.75H40V23.25H45.5V18.75H0V14.25H45.5V9.75H40V5.25H34.5V0.75Z" fill="white"/></svg></div>`;
8
8
 
9
9
  function createModal(promptText, yesCallback, extraContent = "", includeInput = false, defaultValue = "") {
@@ -70,11 +70,11 @@ export function consent(promptText, yesCallback, extraContent = "") {
70
70
  */
71
71
  export function tell(promptText, ...content) {
72
72
  const contentHtml = content.length > 0
73
- ? content.map(c => `<div class="text-[16px] sm:text-[18px] font-normal">${c}</div>`).join("")
73
+ ? content.map(c => `<div class="micromodal__tell-content">${c}</div>`).join("")
74
74
  : "";
75
75
 
76
- themodal.html = `<div class="max-w-[440px] space-y-5 mb-7">
77
- <div class="text-[20px] sm:text-[22px] font-bold">${promptText}</div>
76
+ themodal.html = `<div class="micromodal__tell">
77
+ <div class="micromodal__tell-title">${promptText}</div>
78
78
  ${contentHtml}
79
79
  </div>`;
80
80
  themodal.closeHtml = CLOSE_BUTTON_SVG;
@@ -98,27 +98,22 @@ export function tell(promptText, ...content) {
98
98
 
99
99
  /**
100
100
  * Display a modal with a code snippet and copy functionality
101
- * Following the existing modal pattern from prompts.js
101
+ * @param {string} title - The modal heading
102
+ * @param {string} content - The code to display
103
+ * @param {string} extraContent - Optional warning/info text below the copy button
102
104
  */
103
- export function snippet(title, content, options = {}) {
104
- const {
105
- extraContent = 'Save this, it won\'t be shown again. Expires in 1 year.'
106
- } = options;
105
+ export function snippet(title, content, extraContent = '') {
107
106
 
108
107
  // Create the modal content with copy button
109
108
  const modalContent = `
110
- <div class="bg-[#292E54] p-4 mb-[14px] max-w-[420px]">
111
- <div class="overflow-x-auto">
112
- <pre class="text-white font-mono text-sm whitespace-nowrap">${content}</pre>
113
- </div>
109
+ <div class="snippet-code-block">
110
+ <pre>${content}</pre>
114
111
  </div>
115
112
 
116
- <button type="button" class="custom-button group font-fixedsys text-center cursor-pointer border-[3px] border-t-[#474C65] border-r-[#131725] border-b-[#131725] border-l-[#474C65] bg-[#1D1F2F] hover:bg-[#232639] active:border-b-[#474C65] active:border-l-[#131725] active:border-t-[#131725] active:border-r-[#474C65] text-[23px] p-[2px_16px_4px] w-full mb-4 copy-snippet-btn">
117
- <span class="whitespace-nowrap select-none inline-block group-active:translate-x-[1.5px] group-active:translate-y-[1.5px]">copy</span>
118
- </button>
113
+ <button type="button" class="micromodal__secondary-btn copy-snippet-btn" style="margin-bottom: 14px;">copy</button>
119
114
 
120
115
  ${extraContent ? `
121
- <div class="p-3 border-2 border-[#989742] bg-[#1E1E11] text-sm text-[#FBF7B7] max-w-[420px]">
116
+ <div class="snippet-warning">
122
117
  ${extraContent}
123
118
  </div>
124
119
  ` : ''}
@@ -131,7 +126,7 @@ export function snippet(title, content, options = {}) {
131
126
  </div>`;
132
127
 
133
128
  themodal.closeHtml = CLOSE_BUTTON_SVG;
134
- themodal.yes = CONFIRM_BUTTON_SVG;
129
+ themodal.yes = '';
135
130
 
136
131
  const promise = new Promise((resolve) => {
137
132
  // Local copy function
@@ -429,6 +429,7 @@ const modalCss = `<style class="micromodal-css">
429
429
  align-items: center;
430
430
  width: 100%;
431
431
  height: 39px;
432
+ line-height: 0;
432
433
  border: 3px solid;
433
434
  border-top-color: #94BA6F;
434
435
  border-left-color: #94BA6F;
@@ -449,6 +450,66 @@ const modalCss = `<style class="micromodal-css">
449
450
  border-right-color: #94BA6F;
450
451
  }
451
452
 
453
+ .micromodal button.micromodal__secondary-btn {
454
+ display: flex;
455
+ justify-content: center;
456
+ align-items: center;
457
+ width: 100%;
458
+ height: 39px;
459
+ line-height: 0;
460
+ border: 3px solid;
461
+ border-top-color: #474C65;
462
+ border-left-color: #474C65;
463
+ border-bottom-color: #131725;
464
+ border-right-color: #131725;
465
+ background-color: #1D1F2F;
466
+ color: #E5E7EB;
467
+ font-family: inherit;
468
+ font-size: 16px;
469
+ font-weight: bold;
470
+ }
471
+
472
+ .micromodal button.micromodal__secondary-btn:focus,
473
+ .micromodal button.micromodal__secondary-btn:hover {
474
+ background-color: #232639;
475
+ }
476
+
477
+ .micromodal button.micromodal__secondary-btn:active {
478
+ border-top-color: #131725;
479
+ border-left-color: #131725;
480
+ border-bottom-color: #474C65;
481
+ border-right-color: #474C65;
482
+ }
483
+
484
+ .micromodal:has(.snippet-code-block) .micromodal__content {
485
+ margin-bottom: 0;
486
+ }
487
+
488
+ .micromodal .snippet-code-block {
489
+ background-color: #292E54;
490
+ padding: 1rem;
491
+ margin-bottom: 14px;
492
+ max-width: 420px;
493
+ overflow-x: auto;
494
+ }
495
+
496
+ .micromodal .snippet-code-block pre {
497
+ color: white;
498
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
499
+ font-size: 0.875rem;
500
+ white-space: nowrap;
501
+ margin: 0;
502
+ }
503
+
504
+ .micromodal .snippet-warning {
505
+ padding: 0.75rem;
506
+ border: 2px solid #989742;
507
+ background-color: #1E1E11;
508
+ font-size: 0.875rem;
509
+ color: #FBF7B7;
510
+ max-width: 420px;
511
+ }
512
+
452
513
  .micromodal button.micromodal__close {
453
514
  clip-path: polygon(0% 4%, 0% 0%, 100% 0%, 100% 100%, 94% 100%);
454
515
  position: absolute;
@@ -456,6 +517,46 @@ const modalCss = `<style class="micromodal-css">
456
517
  right: -1px;
457
518
  width: 68px;
458
519
  }
520
+
521
+ .micromodal .micromodal__close-bg {
522
+ fill: #1D2032;
523
+ }
524
+
525
+ .micromodal .micromodal__close:hover .micromodal__close-bg {
526
+ fill: #212543;
527
+ }
528
+
529
+ .micromodal .micromodal__close-x {
530
+ fill: #fff;
531
+ }
532
+
533
+ .micromodal .micromodal__tell {
534
+ max-width: 440px;
535
+ margin-bottom: 28px;
536
+ }
537
+
538
+ .micromodal .micromodal__tell > * + * {
539
+ margin-top: 20px;
540
+ }
541
+
542
+ .micromodal .micromodal__tell-title {
543
+ font-size: 20px;
544
+ font-weight: bold;
545
+ }
546
+
547
+ .micromodal .micromodal__tell-content {
548
+ font-size: 16px;
549
+ font-weight: normal;
550
+ }
551
+
552
+ @media (min-width: 640px) {
553
+ .micromodal .micromodal__tell-title {
554
+ font-size: 22px;
555
+ }
556
+ .micromodal .micromodal__tell-content {
557
+ font-size: 18px;
558
+ }
559
+ }
459
560
  </style>`;
460
561
 
461
562
  const modalHtml = `<div class="micromodal" id="micromodal" aria-hidden="true">
@@ -1,10 +1,10 @@
1
1
  // a nice, simple alert
2
2
  // ❗️ don't use too much text!
3
3
 
4
- // Default modern icons
4
+ // Default modern icons (normalized to 48x48 viewBox)
5
5
  const defaultIcons = {
6
- success: `<svg viewBox="0 0 48 45" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9404 22.4475L21.9099 29.724L35.1906 14.4045M3 3H44.9804V42.809H3V3Z" stroke="#33D131" stroke-width="4.3"/></svg>`,
7
- error: `<svg viewBox="0 0 46 44" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M31.7383 12.4045L13 31.1429M31.7451 31.1429L13.0068 12.4046M2.00977 2H43.9902V41.809H2.00977V2Z" stroke="#FF4450" stroke-width="4"/></svg>`
6
+ success: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.9404 23.9475L21.9099 31.224L35.1906 15.9045M3 4.5H44.9804V44.309H3V4.5Z" stroke="#33D131" stroke-width="4.3"/></svg>`,
7
+ error: `<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M32.7383 14.4045L14 33.1429M32.7451 33.1429L14.0068 14.4046M3.01 4H44.99V43.809H3.01V4Z" stroke="#FF4450" stroke-width="4"/></svg>`
8
8
  };
9
9
 
10
10
  // Hyperclay icons
@@ -100,6 +100,7 @@ const modernStyles = `
100
100
  }
101
101
 
102
102
  [data-toast-theme="modern"] .toast-icon svg {
103
+ display: block;
103
104
  width: 22px;
104
105
  height: 22px;
105
106
  }
@@ -1,36 +0,0 @@
1
- import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
2
- import onDomReady from "../dom-utilities/onDomReady.js";
3
- import {beforeSave} from "./savePage.js";
4
-
5
- export function disableContentEditableBeforeSave () {
6
- beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-contenteditable]').forEach(resource => {
8
- const originalValue = resource.getAttribute("contenteditable");
9
- resource.setAttribute("inert-contenteditable", originalValue);
10
- resource.removeAttribute("contenteditable");
11
- });
12
- });
13
- }
14
-
15
- export function enableContentEditableForAdminOnPageLoad () {
16
- if (!isEditMode) return;
17
-
18
- onDomReady(() => {
19
- document.querySelectorAll('[edit-mode-contenteditable]').forEach(resource => {
20
- let originalValue = resource.getAttribute("inert-contenteditable");
21
-
22
- if (!["false", "plaintext-only"].includes(originalValue)) {
23
- originalValue = "true";
24
- }
25
-
26
- resource.setAttribute("contenteditable", originalValue);
27
- resource.removeAttribute("inert-contenteditable");
28
- });
29
- });
30
- }
31
-
32
- // Auto-initialize
33
- export function init() {
34
- disableContentEditableBeforeSave();
35
- enableContentEditableForAdminOnPageLoad();
36
- }
@@ -1,31 +0,0 @@
1
- import { isEditMode, isOwner } from "./isAdminOfCurrentResource.js";
2
- import onDomReady from "../dom-utilities/onDomReady.js";
3
- import {beforeSave} from "./savePage.js";
4
-
5
- export function disableOnClickBeforeSave () {
6
- beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-onclick]').forEach(resource => {
8
- const originalValue = resource.getAttribute("onclick");
9
- resource.setAttribute("inert-onclick", originalValue);
10
- resource.removeAttribute("onclick");
11
- });
12
- });
13
- }
14
-
15
- export function enableOnClickForAdminOnPageLoad () {
16
- if (!isEditMode) return;
17
-
18
- onDomReady(() => {
19
- document.querySelectorAll('[edit-mode-onclick]').forEach(resource => {
20
- const originalValue = resource.getAttribute("inert-onclick");
21
- resource.setAttribute("onclick", originalValue);
22
- resource.removeAttribute("inert-onclick");
23
- });
24
- });
25
- }
26
-
27
- // Auto-initialize
28
- export function init() {
29
- disableOnClickBeforeSave();
30
- enableOnClickForAdminOnPageLoad();
31
- }
@@ -1,20 +0,0 @@
1
- import Mutation from "../utilities/mutation.js";
2
-
3
- function init() {
4
- const executePageMutation = async element => {
5
- try {
6
- const code = element.getAttribute('onpagemutation');
7
- const asyncFn = new Function(`return (async function() { ${code} })`)();
8
- await asyncFn.call(element);
9
- } catch (error) {
10
- console.error('Error in onpagemutation execution:', error);
11
- }
12
- };
13
-
14
- Mutation.onAnyChange({
15
- debounce: 200,
16
- omitChangeDetails: true
17
- }, () => document.querySelectorAll('[onpagemutation]').forEach(executePageMutation));
18
- }
19
- export { init };
20
- export default init;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes