hyperclayjs 1.22.0 → 1.23.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
@@ -60,7 +60,7 @@ import 'hyperclayjs/presets/standard.js';
60
60
  | autosave | 1.4KB | Auto-save on DOM changes |
61
61
  | edit-mode | 1.8KB | Toggle edit mode on hyperclay on/off |
62
62
  | edit-mode-helpers | 8.3KB | Admin-only functionality: [edit-mode-input], [edit-mode-resource], [edit-mode-onclick] |
63
- | option-visibility | 7.8KB | Dynamic show/hide based on ancestor state with option:attribute="value" |
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 |
@@ -77,6 +77,7 @@ import 'hyperclayjs/presets/standard.js';
77
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
+ | movable | 2.5KB | Free-positioning drag with [movable] and [movable-handle], edit mode only |
80
81
  | onaftersave | 1KB | [onaftersave] attribute - run JS when save status changes |
81
82
  | sortable | 3.4KB | Drag-drop sorting with [sortable], lazy-loads ~118KB Sortable.js in edit mode |
82
83
 
@@ -137,12 +138,12 @@ Essential features for basic editing
137
138
 
138
139
  **Modules:** `save-core`, `snapshot`, `save-system`, `edit-mode-helpers`, `toast`, `save-toast`, `export-to-window`, `view-mode-excludes-edit-modules`
139
140
 
140
- ### Standard (~81.5KB)
141
+ ### Standard (~80.8KB)
141
142
  Standard feature set for most use cases
142
143
 
143
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`
144
145
 
145
- ### Everything (~216.1KB)
146
+ ### Everything (~217.9KB)
146
147
  All available features
147
148
 
148
149
  Includes all available modules across all categories.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperclayjs",
3
- "version": "1.22.0",
3
+ "version": "1.23.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",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Option Visibility (CSS Layers Implementation)
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
- * 1. Uses `display: none !important` to forcefully hide elements
27
- * 2. Uses `display: revert-layer !important` to un-hide when ancestor matches
28
- * `revert-layer` tells the browser: "Ignore rules in this layer, fall back to author styles"
29
- * 3. This preserves the user's original `display` (flex, grid, block) without us knowing what it is
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 `@layer`, `revert-layer`, and `:not()` selector lists (~92% of browsers, 2022+).
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: Simple code, similar to original approach
38
- * - Con: Loses to user `!important` rules (layered !important < unlayered !important)
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:Layer]', ...args);
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 wrapped in @layer
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
- const rules = patterns.map(({ name, rawValue, values, negated }) => {
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
- // Hide rule (same for both types)
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: show when ancestor has attr but NOT any of the values
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
- showRule = `[${safeName}]:not(${notList}) ${attrSelector}{display:revert-layer!important}`;
139
+ const scope = `[${safeName}]:not(${notList})`;
140
+ scopeSelectors = `${scope},${scope} *`;
150
141
  } else {
151
- // option: show when ancestor has ANY of the values
152
- const showSelectors = values.map(v =>
153
- `[${safeName}="${CSS.escape(v)}"] ${attrSelector}`
154
- ).join(',');
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 hideRule + showRule;
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:Layer] Error generating rules:', error);
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.22.0 - Minimal Browser-Native Loader
4
+ * HyperclayJS v1.23.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.
@@ -40,6 +40,7 @@ const MODULE_PATHS = {
40
40
  "event-attrs": "./custom-attributes/events.js",
41
41
  "ajax-elements": "./custom-attributes/ajaxElements.js",
42
42
  "sortable": "./custom-attributes/sortable.js",
43
+ "movable": "./custom-attributes/movable.js",
43
44
  "dom-helpers": "./custom-attributes/domHelpers.js",
44
45
  "input-helpers": "./custom-attributes/inputHelpers.js",
45
46
  "onaftersave": "./custom-attributes/onaftersave.js",
@@ -119,6 +120,7 @@ const PRESETS = {
119
120
  "event-attrs",
120
121
  "ajax-elements",
121
122
  "sortable",
123
+ "movable",
122
124
  "dom-helpers",
123
125
  "input-helpers",
124
126
  "onaftersave",
@@ -162,6 +164,7 @@ const PRESETS = {
162
164
  "event-attrs",
163
165
  "ajax-elements",
164
166
  "sortable",
167
+ "movable",
165
168
  "dom-helpers",
166
169
  "input-helpers",
167
170
  "onaftersave",
@@ -203,6 +206,7 @@ const EDIT_MODE_ONLY = new Set([
203
206
  "persist",
204
207
  "snapshot",
205
208
  "sortable",
209
+ "movable",
206
210
  "onaftersave",
207
211
  "cache-bust",
208
212
  "hyper-morph",