hyperclayjs 1.20.3 → 1.22.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 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 | 7.5KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
62
+ | edit-mode-helpers | 8.3KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
63
63
  | option-visibility | 7.8KB | 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() |
@@ -74,7 +74,7 @@ import 'hyperclayjs/presets/standard.js';
74
74
  | Module | Size | Description |
75
75
  |--------|------|-------------|
76
76
  | ajax-elements | 2.6KB | [ajax-form], [ajax-button] for async form submissions |
77
- | dom-helpers | 6.2KB | el.nearest, el.val, el.text, el.exec, el.cycle |
77
+ | dom-helpers | 6.8KB | el.nearest, el.val, el.text, el.exec, el.cycle |
78
78
  | event-attrs | 4.6KB | [onclickaway], [onclickchildren], [onclone], [onpagemutation], [onrender] |
79
79
  | input-helpers | 3.9KB | [prevent-enter], [autosize] for textareas |
80
80
  | onaftersave | 1KB | [onaftersave] attribute - run JS when save status changes |
@@ -132,17 +132,17 @@ import 'hyperclayjs/presets/standard.js';
132
132
 
133
133
  ## Presets
134
134
 
135
- ### Minimal (~57.8KB)
135
+ ### Minimal (~58.6KB)
136
136
  Essential features for basic editing
137
137
 
138
138
  **Modules:** `save-core`, `snapshot`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
139
139
 
140
- ### Standard (~80.1KB)
140
+ ### Standard (~81.5KB)
141
141
  Standard feature set for most use cases
142
142
 
143
143
  **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`
144
144
 
145
- ### Everything (~214.7KB)
145
+ ### Everything (~216.1KB)
146
146
  All available features
147
147
 
148
148
  Includes all available modules across all categories.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperclayjs",
3
- "version": "1.20.3",
3
+ "version": "1.22.0",
4
4
  "description": "Modular JavaScript library for building interactive malleable HTML files with Hyperclay",
5
5
  "type": "module",
6
6
  "main": "src/hyperclay.js",
@@ -24,7 +24,8 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "dev": "npm run build && npm run build:website && http-server website -p 3535 -c-1 -o /index.html",
27
- "build": "npm run generate:deps && npm run build:loader && npm run build:readme && npm run build:load-jsdelivr && npm run build:index-url",
27
+ "build": "npm run generate:deps && npm run build:loader && npm run build:readme && npm run build:load-jsdelivr && npm run build:index-url && npm run build:llms-txt",
28
+ "build:llms-txt": "node build/build-llms-txt.js",
28
29
  "build:website": "node scripts/build-website.js",
29
30
  "generate:deps": "node build/generate-dependency-graph.js",
30
31
  "build:loader": "node build/build-loader.js",
@@ -2,9 +2,11 @@ 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 = '[edit-mode-contenteditable], [editmode\\:contenteditable]';
6
+
5
7
  export function disableContentEditableBeforeSave () {
6
8
  beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-contenteditable]').forEach(resource => {
9
+ docElem.querySelectorAll(SELECTOR).forEach(resource => {
8
10
  const originalValue = resource.getAttribute("contenteditable");
9
11
  resource.setAttribute("inert-contenteditable", originalValue);
10
12
  resource.removeAttribute("contenteditable");
@@ -22,7 +24,7 @@ export function enableContentEditableForAdminOnPageLoad () {
22
24
 
23
25
  // Runtime toggle functions
24
26
  export function enableContentEditable() {
25
- document.querySelectorAll('[edit-mode-contenteditable]').forEach(el => {
27
+ document.querySelectorAll(SELECTOR).forEach(el => {
26
28
  let val = el.getAttribute("inert-contenteditable");
27
29
  if (!["false", "plaintext-only"].includes(val)) val = "true";
28
30
  el.setAttribute("contenteditable", val);
@@ -31,7 +33,7 @@ export function enableContentEditable() {
31
33
  }
32
34
 
33
35
  export function disableContentEditable() {
34
- document.querySelectorAll('[edit-mode-contenteditable]').forEach(el => {
36
+ document.querySelectorAll(SELECTOR).forEach(el => {
35
37
  const val = el.getAttribute("contenteditable") || "true";
36
38
  el.setAttribute("inert-contenteditable", val);
37
39
  el.removeAttribute("contenteditable");
@@ -48,4 +50,4 @@ export function init() {
48
50
  window.hyperclay = window.hyperclay || {};
49
51
  window.hyperclay.enableContentEditable = enableContentEditable;
50
52
  window.hyperclay.disableContentEditable = disableContentEditable;
51
- window.h = window.hyperclay;
53
+ window.h = window.hyperclay;
@@ -2,15 +2,24 @@ 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 = '[edit-mode-input], [viewmode\\:disabled]';
6
+ const SELECTOR_READONLY = '[viewmode\\:readonly]';
7
+ const SELECTOR_ALL = '[edit-mode-input], [viewmode\\:disabled], [viewmode\\:readonly]';
8
+
5
9
  export function disableAdminInputsBeforeSave() {
6
10
  beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-input]').forEach(input => {
8
- if (supportsReadonly(input)) {
11
+ docElem.querySelectorAll(SELECTOR_DISABLED).forEach(input => {
12
+ if (input.hasAttribute('viewmode:disabled')) {
13
+ input.setAttribute('disabled', '');
14
+ } else if (supportsReadonly(input)) {
9
15
  input.setAttribute('readonly', '');
10
16
  } else {
11
17
  input.setAttribute('disabled', '');
12
18
  }
13
19
  });
20
+ docElem.querySelectorAll(SELECTOR_READONLY).forEach(input => {
21
+ input.setAttribute('readonly', '');
22
+ });
14
23
  });
15
24
  }
16
25
 
@@ -24,45 +33,49 @@ export function enableAdminInputsOnPageLoad() {
24
33
 
25
34
  // Runtime toggle functions
26
35
  export function enableAdminInputs() {
27
- document.querySelectorAll('[edit-mode-input]').forEach(input => {
28
- if (supportsReadonly(input)) {
36
+ document.querySelectorAll(SELECTOR_DISABLED).forEach(input => {
37
+ if (input.hasAttribute('viewmode:disabled')) {
38
+ input.removeAttribute('disabled');
39
+ } else if (supportsReadonly(input)) {
29
40
  input.removeAttribute('readonly');
30
41
  } else {
31
42
  input.removeAttribute('disabled');
32
43
  }
33
44
  });
45
+ document.querySelectorAll(SELECTOR_READONLY).forEach(input => {
46
+ input.removeAttribute('readonly');
47
+ });
34
48
  }
35
49
 
36
50
  export function disableAdminInputs() {
37
- document.querySelectorAll('[edit-mode-input]').forEach(input => {
38
- if (supportsReadonly(input)) {
51
+ document.querySelectorAll(SELECTOR_DISABLED).forEach(input => {
52
+ if (input.hasAttribute('viewmode:disabled')) {
53
+ input.setAttribute('disabled', '');
54
+ } else if (supportsReadonly(input)) {
39
55
  input.setAttribute('readonly', '');
40
56
  } else {
41
57
  input.setAttribute('disabled', '');
42
58
  }
43
59
  });
60
+ document.querySelectorAll(SELECTOR_READONLY).forEach(input => {
61
+ input.setAttribute('readonly', '');
62
+ });
44
63
  }
45
64
 
46
65
  // Input types that support the readonly attribute
47
66
  const readonlyTypes = ['text', 'search', 'url', 'tel', 'email', 'password', 'date', 'month', 'week', 'time', 'datetime-local', 'number'];
48
67
 
49
68
  function supportsReadonly(element) {
50
- // Handle different element types
51
69
  const tagName = element.tagName?.toUpperCase();
52
70
 
53
- // TEXTAREA supports readonly
54
71
  if (tagName === 'TEXTAREA') return true;
55
-
56
- // SELECT, BUTTON, FIELDSET use disabled
57
72
  if (tagName === 'SELECT' || tagName === 'BUTTON' || tagName === 'FIELDSET') return false;
58
73
 
59
- // For INPUT elements, check the type
60
74
  if (tagName === 'INPUT') {
61
75
  const type = element.type || 'text';
62
76
  return readonlyTypes.includes(type);
63
77
  }
64
78
 
65
- // Default to disabled for unknown elements
66
79
  return false;
67
80
  }
68
81
 
@@ -76,4 +89,4 @@ export function init() {
76
89
  window.hyperclay = window.hyperclay || {};
77
90
  window.hyperclay.enableAdminInputs = enableAdminInputs;
78
91
  window.hyperclay.disableAdminInputs = disableAdminInputs;
79
- window.h = window.hyperclay;
92
+ window.h = window.hyperclay;
@@ -2,9 +2,11 @@ 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 = '[edit-mode-onclick], [editmode\\:onclick]';
6
+
5
7
  export function disableOnClickBeforeSave () {
6
8
  beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-onclick]').forEach(resource => {
9
+ docElem.querySelectorAll(SELECTOR).forEach(resource => {
8
10
  const originalValue = resource.getAttribute("onclick");
9
11
  resource.setAttribute("inert-onclick", originalValue);
10
12
  resource.removeAttribute("onclick");
@@ -22,7 +24,7 @@ export function enableOnClickForAdminOnPageLoad () {
22
24
 
23
25
  // Runtime toggle functions
24
26
  export function enableOnClick() {
25
- document.querySelectorAll('[edit-mode-onclick]').forEach(el => {
27
+ document.querySelectorAll(SELECTOR).forEach(el => {
26
28
  const val = el.getAttribute("inert-onclick");
27
29
  if (val) {
28
30
  el.setAttribute("onclick", val);
@@ -32,7 +34,7 @@ export function enableOnClick() {
32
34
  }
33
35
 
34
36
  export function disableOnClick() {
35
- document.querySelectorAll('[edit-mode-onclick]').forEach(el => {
37
+ document.querySelectorAll(SELECTOR).forEach(el => {
36
38
  const val = el.getAttribute("onclick");
37
39
  if (val) {
38
40
  el.setAttribute("inert-onclick", val);
@@ -51,4 +53,4 @@ export function init() {
51
53
  window.hyperclay = window.hyperclay || {};
52
54
  window.hyperclay.enableOnClick = enableOnClick;
53
55
  window.hyperclay.disableOnClick = disableOnClick;
54
- window.h = window.hyperclay;
56
+ window.h = window.hyperclay;
@@ -2,11 +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 = '[edit-mode-resource]:is(style, link, script), [editmode\\:resource]:is(style, link, script)';
6
+ const SELECTOR_INERT = '[edit-mode-resource]:is(style, link, script)[type^="inert/"], [editmode\\:resource]:is(style, link, script)[type^="inert/"]';
7
+
5
8
  export function disableAdminResourcesBeforeSave () {
6
9
  beforeSave(docElem => {
7
- docElem.querySelectorAll('[edit-mode-resource]:is(style, link, script)').forEach(resource => {
10
+ docElem.querySelectorAll(SELECTOR).forEach(resource => {
8
11
  const currentType = resource.getAttribute('type') || 'text/javascript';
9
- // Only add the inert/ prefix if it's not already there
10
12
  if (!currentType.startsWith('inert/')) {
11
13
  resource.setAttribute('type', `inert/${currentType}`);
12
14
  }
@@ -24,14 +26,14 @@ export function enableAdminResourcesOnPageLoad () {
24
26
 
25
27
  // Runtime toggle functions
26
28
  export function enableAdminResources() {
27
- document.querySelectorAll('[edit-mode-resource]:is(style, link, script)[type^="inert/"]').forEach(resource => {
29
+ document.querySelectorAll(SELECTOR_INERT).forEach(resource => {
28
30
  resource.type = resource.type.replace(/inert\//g, '');
29
31
  resource.replaceWith(resource.cloneNode(true));
30
32
  });
31
33
  }
32
34
 
33
35
  export function disableAdminResources() {
34
- document.querySelectorAll('[edit-mode-resource]:is(style, link, script)').forEach(resource => {
36
+ document.querySelectorAll(SELECTOR).forEach(resource => {
35
37
  const currentType = resource.getAttribute('type') || 'text/javascript';
36
38
  if (!currentType.startsWith('inert/')) {
37
39
  resource.setAttribute('type', `inert/${currentType}`);
@@ -50,4 +52,4 @@ export function init() {
50
52
  window.hyperclay = window.hyperclay || {};
51
53
  window.hyperclay.enableAdminResources = enableAdminResources;
52
54
  window.hyperclay.disableAdminResources = disableAdminResources;
53
- window.h = window.hyperclay;
55
+ window.h = window.hyperclay;
@@ -8,7 +8,23 @@ function init () {
8
8
  return;
9
9
  }
10
10
 
11
+ // elem.el.menu returns nearest element with a "menu" attribute (alias for elem.nearest.menu)
11
12
  // elem.nearest.project returns nearest element with a "project" attribute
13
+ Object.defineProperty(HTMLElement.prototype, 'el', {
14
+ configurable: true,
15
+ get: function() {
16
+ let element = this;
17
+
18
+ const handler = {
19
+ get(target, prop) {
20
+ return nearest(element, `[${prop}], .${prop}`);
21
+ }
22
+ };
23
+
24
+ return new Proxy({}, handler);
25
+ }
26
+ });
27
+
12
28
  Object.defineProperty(HTMLElement.prototype, 'nearest', {
13
29
  configurable: true,
14
30
  get: function() {
@@ -183,6 +199,16 @@ function init () {
183
199
  if (next) this.setAttribute(setAttr, next);
184
200
  };
185
201
 
202
+ Element.prototype.show = function() {
203
+ this.style.display = '';
204
+ return this;
205
+ };
206
+
207
+ Element.prototype.hide = function() {
208
+ this.style.display = 'none';
209
+ return this;
210
+ };
211
+
186
212
  }
187
213
 
188
214
  // Auto-export to window unless suppressed by loader
@@ -0,0 +1,93 @@
1
+ /*
2
+
3
+ Make elements freely positionable by dragging
4
+
5
+ How to use:
6
+ - add `movable` attribute to an element to make it draggable
7
+ - e.g. <div movable style="transform: translate(100px, 50px)">...</div>
8
+ - add `movable-handle` to a child to restrict the grab zone
9
+ - e.g. <div movable><div movable-handle>⠿</div>...</div>
10
+ - without a handle, the entire element is grabbable
11
+
12
+ Position is stored directly in the element's transform style.
13
+ While dragging, the element gets a `movable-dragging` attribute.
14
+
15
+ */
16
+ import { isEditMode } from "../core/isAdminOfCurrentResource.js";
17
+ import Mutation from "../utilities/mutation.js";
18
+
19
+ let drag = null;
20
+ let zIndex = 0;
21
+
22
+ function parseTranslate(el) {
23
+ const match = el.style.transform?.match(/translate\((-?\d+(?:\.\d+)?)px,\s*(-?\d+(?:\.\d+)?)px\)/);
24
+ return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 };
25
+ }
26
+
27
+ function onPointerDown(e) {
28
+ const handle = e.target.closest('[movable-handle]');
29
+ const movable = e.target.closest('[movable]');
30
+ if (!movable) return;
31
+
32
+ if (handle) {
33
+ if (!movable.contains(handle)) return;
34
+ } else {
35
+ if (movable.querySelector('[movable-handle]')) return;
36
+ }
37
+
38
+ const pos = parseTranslate(movable);
39
+ const scrollEl = movable.parentElement;
40
+
41
+ drag = {
42
+ el: movable,
43
+ offsetX: e.clientX - pos.x + (scrollEl?.scrollLeft || 0),
44
+ offsetY: e.clientY - pos.y + (scrollEl?.scrollTop || 0),
45
+ scrollEl
46
+ };
47
+
48
+ movable.setAttribute('movable-dragging', '');
49
+ movable.style.zIndex = ++zIndex;
50
+ e.preventDefault();
51
+ }
52
+
53
+ function onPointerMove(e) {
54
+ if (!drag) return;
55
+ const x = Math.max(0, e.clientX - drag.offsetX + (drag.scrollEl?.scrollLeft || 0));
56
+ const y = Math.max(0, e.clientY - drag.offsetY + (drag.scrollEl?.scrollTop || 0));
57
+ drag.el.style.transform = `translate(${x}px, ${y}px)`;
58
+ }
59
+
60
+ function onPointerUp() {
61
+ if (!drag) return;
62
+ drag.el.removeAttribute('movable-dragging');
63
+ drag = null;
64
+ }
65
+
66
+ function makeMovable(el) {
67
+ if (!el.style.transform) {
68
+ el.style.transform = 'translate(0px, 0px)';
69
+ }
70
+ }
71
+
72
+ function init() {
73
+ if (!isEditMode) return;
74
+
75
+ document.querySelectorAll('[movable]').forEach(makeMovable);
76
+
77
+ document.addEventListener('pointerdown', onPointerDown);
78
+ document.addEventListener('pointermove', onPointerMove);
79
+ document.addEventListener('pointerup', onPointerUp);
80
+ document.addEventListener('pointercancel', onPointerUp);
81
+
82
+ Mutation.onAddElement({
83
+ selectorFilter: "[movable]",
84
+ debounce: 200
85
+ }, (changes) => {
86
+ changes.forEach(({ element }) => makeMovable(element));
87
+ });
88
+ }
89
+
90
+ init();
91
+
92
+ export { init };
93
+ 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.20.3 - Minimal Browser-Native Loader
4
+ * HyperclayJS v1.22.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.