hyper-undo 0.5.0 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # hyper-undo changelog
2
2
 
3
+ ## [0.5.2] - 2026-09-04
4
+
5
+ ### Fixed
6
+ - Undo and redo inside a live richclay text region now reach the editor instead of the page. `[data-richclay-active]` joins the default `shadowKeydownIn` list, alongside CodeMirror, Monaco, Ace, Quill, TipTap and ProseMirror. richclay stamps `no-undo` on its region, so the page-level stack holds no record of what was typed there; the capture-phase key handler nevertheless swallowed Cmd+Z, which meant the keystroke never reached the editor's own stack and the page instead reverted an unrelated earlier edit. richclay has always intended this deferral, and this is the other half of it.
7
+
8
+ ## [0.5.1] - 2026-08-21
9
+
10
+ ### Added
11
+ - CONTRIBUTING.md stating that contributions are accepted under MIT-0
12
+
13
+ ### Changed
14
+ - Relicensed from MIT to MIT-0 (MIT No Attribution); attribution is no longer required
15
+ - Node tests now await the scope's next `commit` event (`nextCommit` helper) instead of fixed wall-clock waits, making the suite less timing-sensitive
16
+
17
+
18
+
19
+ ## [Unreleased]
20
+
21
+ ### Changed
22
+ - License: relicensed to MIT-0 (MIT No Attribution). Same rights, attribution no longer required.
23
+
3
24
  ## [0.5.0] - 2026-08-12
4
25
 
5
26
  ### Added
package/LICENSE CHANGED
@@ -1,21 +1,16 @@
1
- MIT License
1
+ MIT No Attribution
2
2
 
3
3
  Copyright (c) 2026 David Miranda
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
6
+ software and associated documentation files (the "Software"), to deal in the Software
7
+ without restriction, including without limitation the rights to use, copy, modify,
8
+ merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so.
11
10
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
12
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
13
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
14
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
16
+ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -158,7 +158,7 @@ raw typing into a field isn't directly observable. Coverage:
158
158
 
159
159
  - **CMS form fields** flow through the engine, which mutates the page DOM — the
160
160
  recorder sees the page mutation. ✓
161
- - **`[persist]` inputs** on a Hyperclay page mirror `el.value` to the `value`
161
+ - **`[persist]` inputs** on a Hyperclay™ page mirror `el.value` to the `value`
162
162
  attribute; **`[persist]` textareas** mirror to `data-value`. The recorder sees
163
163
  the attribute mutation. ✓
164
164
  - **Plain `<input>`/`<textarea>` without `[persist]`** do NOT mirror; their
@@ -176,4 +176,4 @@ of non-DOM state, no "revert to saved" checkpoint.
176
176
 
177
177
  ## License
178
178
 
179
- MIT
179
+ MIT-0 (MIT No Attribution).
@@ -1 +1 @@
1
- var hyperundo=(()=>{var T=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of te(t))!re.call(e,o)&&o!==n&&T(e,o,{get:()=>t[o],enumerable:!(r=ee(t,o))||r.enumerable});return e};var ae=e=>oe(T({},"__esModule",{value:!0}),e);var me={};ne(me,{default:()=>pe,undo:()=>C});function ie(e,t=null){let n=[];if(e.type==="attributes"){let r=e.target,o=e.attributeName,i=e.oldValue,u=r.getAttribute(o);return i==null&&u!=null?n.push({kind:"attr-add",target:r,name:o,newValue:u}):i!=null&&u==null?n.push({kind:"attr-remove",target:r,name:o,oldValue:i}):i!==u&&n.push({kind:"attr-set",target:r,name:o,oldValue:i,newValue:u}),n}if(e.type==="characterData")return n.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),n;if(e.type==="childList"){let r=e.target,o=e.nextSibling,i=t?Array.from(e.addedNodes).filter(d=>!t(d)):Array.from(e.addedNodes),u=t?Array.from(e.removedNodes).filter(d=>!t(d)):Array.from(e.removedNodes);return i.length>0&&n.push({kind:"add",parent:r,nodes:i,before:o}),u.length>0&&n.push({kind:"remove",parent:r,nodes:u,before:o}),n}return n}function P(e,t=null){let n=[],r=new Map;for(let o of e){if(o.type==="attributes"){let i=o.target,u=o.attributeName,d=r.get(i);d||(d=new Map,r.set(i,d)),d.has(u)||(d.set(u,{idx:n.length,oldValue:o.oldValue}),n.push(null));continue}for(let i of ie(o,t))n.push(i)}for(let[o,i]of r)for(let[u,d]of i){let l=d.oldValue,m=o.getAttribute(u),p=null;l===m?p=null:l==null?p={kind:"attr-add",target:o,name:u,newValue:m}:m==null?p={kind:"attr-remove",target:o,name:u,oldValue:l}:p={kind:"attr-set",target:o,name:u,oldValue:l,newValue:m},n[d.idx]=p}return n.filter(o=>o!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return;case"remove":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return;case"remove":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return}}var se=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],ue=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=se.join(","),U=new RegExp("^(?:"+ue.join("|")+")(?:-|$)");var de=["mutations-ignore","save-remove","save-ignore","save-freeze","no-undo","no-watch"];function le(){if(typeof window>"u")return null;let e=window.clay?.region||window.hyperclay?.region;return e&&typeof e.resolveRegionPolicy=="function"?e.resolveRegionPolicy:null}function ce(e){let t=le();if(t)try{return!t(e).undoable}catch{}let n=e&&e.nodeType!==1?e.parentElement:e;if(n&&n.closest&&n.closest(M))return!0;for(;n&&n.nodeType===1;){for(let r of de)if(n.hasAttribute&&n.hasAttribute(r))return!0;n=n.parentElement}return!1}function R(e,t,n){if(ce(e))return!0;if(n&&n.type==="attributes"){if(n.attributeName&&U.test(n.attributeName.toLowerCase()))return!0;if(t)try{if(t(n.attributeName,n.target))return!0}catch{}}return!1}function X(){let e=new Map;function t(o,i){let u=e.get(o);return u||(u=new Set,e.set(o,u)),u.add(i),()=>n(o,i)}function n(o,i){let u=e.get(o);u&&u.delete(i)}function r(o,i){let u=e.get(o);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:t,off:n,emit:r}}var fe={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function N(e){let t={...fe,...e};if(!t.scope)throw new Error("hyper-undo: scope is required");let n=X(),r=[],o=[],i=[],u=null,d=null,l=0,m=!1;function p(...s){t.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=W(g),d.observe(t.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),p("started"))}function W(s){let c=typeof document<"u"&&t.scope===document.body,f=typeof window<"u"&&(window.clay?.Mutation||window.hyperclay?.Mutation)||null;return c&&f&&typeof f.createObserver=="function"?(p("sourcing records from the platform Mutation hub (shared observer)"),f.createObserver(s)):new MutationObserver(s)}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),y(),r.length=0,o.length=0,i=[],l=0,p("stopped"))}function V(){let s=t.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function g(s){if(l>0)return;let c=_(s);if(c.length>0){for(let f of c)i.push(f);I()}}function _(s){let c=s.filter(f=>!R(f.target,t.ignoreAttribute,f));return P(c,f=>R(f,t.ignoreAttribute))}function I(){y(),u=setTimeout(K,t.idleWindowMs)}function y(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],v(t.idleLabel,s)}function v(s,c){for(r.push({label:s,timestamp:Date.now(),primitives:c}),o.length=0;r.length>t.maxHistory;)r.shift();p("commit",s,"primitives:",c.length),n.emit("commit")}function j(s,c){if(!m)throw new Error("hyper-undo: scope not started");g(d.takeRecords()),w();let f=c();if(f&&typeof f.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(g(d.takeRecords()),i.length===0){n.emit("commit");return}y();let b=i;i=[],v(s,b)}function G(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(y(),v(s,c))}function Z(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function $(s,{prop:c="value",oldValue:f,newValue:b}={}){!s||f===b||O({kind:"value",target:s,prop:c,oldValue:f,newValue:b})}function w(){l===0&&d&&g(d.takeRecords()),i.length!==0&&(y(),K())}function k(){l===0&&d&&g(d.takeRecords()),l++,p("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),p("resumed",l))}function J(){if(V()){E();return}w();let s=r.pop();if(s){k();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(f){p("undo primitive failed (continuing)",f)}}finally{x()}o.push(s),p("undo",s.label),n.emit("undo")}}function Q(){if(V()){E();return}w();let s=o.pop();if(s){k();try{for(let c of s.primitives)try{L(c)}catch(f){p("redo primitive failed (continuing)",f)}}finally{x()}r.push(s),p("redo",s.label),n.emit("redo")}}function Y(){d&&d.takeRecords(),r.length=0,o.length=0,i=[],y(),p("cleared"),n.emit("clear")}return{_config:t,start:H,stop:E,commit:j,commitCaptured:G,discardCaptured:Z,flush:w,record:O,recordValue:$,undo:J,redo:Q,clear:Y,pause:k,resume:x,on:n.on,off:n.off,get canUndo(){return r.length>0},get canRedo(){return o.length>0},get isPaused(){return l>0},get history(){return r.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return t.scope}}}function q(e,t){let n=r=>{let o=e.scope;if(o&&o.nodeType===1&&o.isConnected===!1){t&&t();return}let i=r.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(r.metaKey||r.ctrlKey))return;let l=(r.key||"").toLowerCase();if(l==="z"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.undo();return}if(l==="z"&&r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}if(l==="y"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",n,!0),()=>window.removeEventListener("keydown",n,!0)}var A=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror"],a=null,S=null,z=null,h=null;function F(e,t){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let n=q(e,t);return h={inst:e,cleanup:n},n}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var C={defaults:{shadowKeydownIn:A},start(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(S===t)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A};return a=N(n),a.start(),S=t,e.bindKeys!==!1&&(z=F(a,()=>C.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,S=null)},create(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A},r=N(n),o=null,i={_config:r._config,commit:r.commit,commitCaptured:r.commitCaptured,discardCaptured:r.discardCaptured,flush:r.flush,record:r.record,recordValue:r.recordValue,undo:r.undo,redo:r.redo,clear:r.clear,pause:r.pause,resume:r.resume,on:r.on,off:r.off,get canUndo(){return r.canUndo},get canRedo(){return r.canRedo},get isPaused(){return r.isPaused},get history(){return r.history},get scope(){return r.scope},start(){e.bindKeys&&(o=F(r,()=>i.stop())),r.start()},stop(){B(r),o=null,r.stop()}};return i},commit(e,t){return a?a.commit(e,t):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,t){return a?a.recordValue(e,t):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,t){return a?a.on(e,t):void 0},off(e,t){return a?a.off(e,t):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var pe=C;return ae(me);})();
1
+ var hyperundo=(()=>{var T=Object.defineProperty;var ee=Object.getOwnPropertyDescriptor;var te=Object.getOwnPropertyNames;var re=Object.prototype.hasOwnProperty;var ne=(e,t)=>{for(var n in t)T(e,n,{get:t[n],enumerable:!0})},oe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of te(t))!re.call(e,o)&&o!==n&&T(e,o,{get:()=>t[o],enumerable:!(r=ee(t,o))||r.enumerable});return e};var ae=e=>oe(T({},"__esModule",{value:!0}),e);var me={};ne(me,{default:()=>pe,undo:()=>C});function ie(e,t=null){let n=[];if(e.type==="attributes"){let r=e.target,o=e.attributeName,i=e.oldValue,u=r.getAttribute(o);return i==null&&u!=null?n.push({kind:"attr-add",target:r,name:o,newValue:u}):i!=null&&u==null?n.push({kind:"attr-remove",target:r,name:o,oldValue:i}):i!==u&&n.push({kind:"attr-set",target:r,name:o,oldValue:i,newValue:u}),n}if(e.type==="characterData")return n.push({kind:"text",target:e.target,oldValue:e.oldValue,newValue:e.target.data}),n;if(e.type==="childList"){let r=e.target,o=e.nextSibling,i=t?Array.from(e.addedNodes).filter(d=>!t(d)):Array.from(e.addedNodes),u=t?Array.from(e.removedNodes).filter(d=>!t(d)):Array.from(e.removedNodes);return i.length>0&&n.push({kind:"add",parent:r,nodes:i,before:o}),u.length>0&&n.push({kind:"remove",parent:r,nodes:u,before:o}),n}return n}function P(e,t=null){let n=[],r=new Map;for(let o of e){if(o.type==="attributes"){let i=o.target,u=o.attributeName,d=r.get(i);d||(d=new Map,r.set(i,d)),d.has(u)||(d.set(u,{idx:n.length,oldValue:o.oldValue}),n.push(null));continue}for(let i of ie(o,t))n.push(i)}for(let[o,i]of r)for(let[u,d]of i){let l=d.oldValue,m=o.getAttribute(u),p=null;l===m?p=null:l==null?p={kind:"attr-add",target:o,name:u,newValue:m}:m==null?p={kind:"attr-remove",target:o,name:u,oldValue:l}:p={kind:"attr-set",target:o,name:u,oldValue:l,newValue:m},n[d.idx]=p}return n.filter(o=>o!=null)}function L(e){switch(e.kind){case"attr-set":case"attr-add":e.target.setAttribute(e.name,e.newValue);return;case"attr-remove":e.target.removeAttribute(e.name);return;case"text":e.target.data=e.newValue;return;case"value":e.target[e.prop]=e.newValue;return;case"add":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return;case"remove":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return}}function D(e){switch(e.kind){case"attr-set":e.target.setAttribute(e.name,e.oldValue);return;case"attr-add":e.target.removeAttribute(e.name);return;case"attr-remove":e.target.setAttribute(e.name,e.oldValue);return;case"text":e.target.data=e.oldValue;return;case"value":e.target[e.prop]=e.oldValue;return;case"add":for(let t of e.nodes)t.parentNode===e.parent&&e.parent.removeChild(t);return;case"remove":for(let t of e.nodes)e.before&&e.before.parentNode===e.parent?e.parent.insertBefore(t,e.before):e.parent.appendChild(t);return}}var se=['[src^="chrome-extension://"]','[href^="chrome-extension://"]','[src^="moz-extension://"]','[href^="moz-extension://"]','[src^="safari-web-extension://"]','[href^="safari-web-extension://"]','[id="1p-menu-live-region"]',"com-1password-button","com-1password-menu","com-1password-notification","grammarly-extension","grammarly-desktop-integration","lt-div","lt-card","lt-toolbar","lt-highlighter","[data-lastpass-root]","[data-lastpass-icon-root]","[data-dashlane-shadowhost]","[data-klarna-extension]",'[class*="klarna-extension"]',".skiptranslate","#goog-gt-tt",".goog-te-banner-frame",".goog-te-balloon-frame","style.darkreader","style[data-darkreader-mode]","style[data-darkreader-scheme]","#loom-companion-mv3","loom-container","[ext-id]"],ue=["data-grammarly","data-lt","data-1p","data-com-onepassword","data-lastpass","data-lp","data-kwimpala","data-bitwarden","data-bw","data-dashlane","data-np","data-protonpass","data-proton-pass","data-keeper","data-rf","data-roboform","data-honey","data-rakuten","data-capital-one","data-wikibuy","data-klarna","data-darkreader","data-evernote","data-pocket","data-pin"],M=se.join(","),U=new RegExp("^(?:"+ue.join("|")+")(?:-|$)");var de=["mutations-ignore","save-remove","save-ignore","save-freeze","no-undo","no-watch"];function le(){if(typeof window>"u")return null;let e=window.clay?.region||window.hyperclay?.region;return e&&typeof e.resolveRegionPolicy=="function"?e.resolveRegionPolicy:null}function ce(e){let t=le();if(t)try{return!t(e).undoable}catch{}let n=e&&e.nodeType!==1?e.parentElement:e;if(n&&n.closest&&n.closest(M))return!0;for(;n&&n.nodeType===1;){for(let r of de)if(n.hasAttribute&&n.hasAttribute(r))return!0;n=n.parentElement}return!1}function R(e,t,n){if(ce(e))return!0;if(n&&n.type==="attributes"){if(n.attributeName&&U.test(n.attributeName.toLowerCase()))return!0;if(t)try{if(t(n.attributeName,n.target))return!0}catch{}}return!1}function X(){let e=new Map;function t(o,i){let u=e.get(o);return u||(u=new Set,e.set(o,u)),u.add(i),()=>n(o,i)}function n(o,i){let u=e.get(o);u&&u.delete(i)}function r(o,i){let u=e.get(o);if(u)for(let d of Array.from(u))try{d(i)}catch(l){console.error("[hyper-undo] listener threw",l)}}return{on:t,off:n,emit:r}}var fe={maxHistory:100,idleWindowMs:500,idleLabel:"Edit",ignoreAttribute:null,debug:!1};function N(e){let t={...fe,...e};if(!t.scope)throw new Error("hyper-undo: scope is required");let n=X(),r=[],o=[],i=[],u=null,d=null,l=0,m=!1;function p(...s){t.debug&&console.log("[hyper-undo]",...s)}function H(){m||(m=!0,d=W(g),d.observe(t.scope,{childList:!0,attributes:!0,characterData:!0,subtree:!0,attributeOldValue:!0,characterDataOldValue:!0}),p("started"))}function W(s){let c=typeof document<"u"&&t.scope===document.body,f=typeof window<"u"&&(window.clay?.Mutation||window.hyperclay?.Mutation)||null;return c&&f&&typeof f.createObserver=="function"?(p("sourcing records from the platform Mutation hub (shared observer)"),f.createObserver(s)):new MutationObserver(s)}function E(){m&&(m=!1,d&&(d.disconnect(),d=null),y(),r.length=0,o.length=0,i=[],l=0,p("stopped"))}function V(){let s=t.scope;return!!(s&&s.nodeType===1&&s.isConnected===!1)}function g(s){if(l>0)return;let c=_(s);if(c.length>0){for(let f of c)i.push(f);I()}}function _(s){let c=s.filter(f=>!R(f.target,t.ignoreAttribute,f));return P(c,f=>R(f,t.ignoreAttribute))}function I(){y(),u=setTimeout(K,t.idleWindowMs)}function y(){u!=null&&(clearTimeout(u),u=null)}function K(){if(u=null,i.length===0)return;let s=i;i=[],v(t.idleLabel,s)}function v(s,c){for(r.push({label:s,timestamp:Date.now(),primitives:c}),o.length=0;r.length>t.maxHistory;)r.shift();p("commit",s,"primitives:",c.length),n.emit("commit")}function j(s,c){if(!m)throw new Error("hyper-undo: scope not started");g(d.takeRecords()),w();let f=c();if(f&&typeof f.then=="function")throw new Error("hyper-undo: commit() fn must be synchronous; returned a Promise");if(g(d.takeRecords()),i.length===0){n.emit("commit");return}y();let b=i;i=[],v(s,b)}function G(s){if(!m)throw new Error("hyper-undo: scope not started");if(!d)return;if(l>1){d.takeRecords();return}w();let c=_(d.takeRecords());c.length!==0&&(y(),v(s,c))}function Z(){d&&d.takeRecords()}function O(s){!m||l>0||!s||(i.push(s),I())}function $(s,{prop:c="value",oldValue:f,newValue:b}={}){!s||f===b||O({kind:"value",target:s,prop:c,oldValue:f,newValue:b})}function w(){l===0&&d&&g(d.takeRecords()),i.length!==0&&(y(),K())}function k(){l===0&&d&&g(d.takeRecords()),l++,p("paused",l)}function x(){l!==0&&(l--,l===0&&d&&d.takeRecords(),p("resumed",l))}function J(){if(V()){E();return}w();let s=r.pop();if(s){k();try{for(let c=s.primitives.length-1;c>=0;c--)try{D(s.primitives[c])}catch(f){p("undo primitive failed (continuing)",f)}}finally{x()}o.push(s),p("undo",s.label),n.emit("undo")}}function Q(){if(V()){E();return}w();let s=o.pop();if(s){k();try{for(let c of s.primitives)try{L(c)}catch(f){p("redo primitive failed (continuing)",f)}}finally{x()}r.push(s),p("redo",s.label),n.emit("redo")}}function Y(){d&&d.takeRecords(),r.length=0,o.length=0,i=[],y(),p("cleared"),n.emit("clear")}return{_config:t,start:H,stop:E,commit:j,commitCaptured:G,discardCaptured:Z,flush:w,record:O,recordValue:$,undo:J,redo:Q,clear:Y,pause:k,resume:x,on:n.on,off:n.off,get canUndo(){return r.length>0},get canRedo(){return o.length>0},get isPaused(){return l>0},get history(){return r.map(s=>({label:s.label,timestamp:s.timestamp}))},get scope(){return t.scope}}}function q(e,t){let n=r=>{let o=e.scope;if(o&&o.nodeType===1&&o.isConnected===!1){t&&t();return}let i=r.target,u=e._config.shadowKeydownIn;if(i&&i.closest&&Array.isArray(u))for(let m of u)try{if(i.closest(m))return}catch{}if(!(r.metaKey||r.ctrlKey))return;let l=(r.key||"").toLowerCase();if(l==="z"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.undo();return}if(l==="z"&&r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}if(l==="y"&&!r.shiftKey){r.preventDefault(),r.stopPropagation(),e.redo();return}};return window.addEventListener("keydown",n,!0),()=>window.removeEventListener("keydown",n,!0)}var A=[".CodeMirror",".cm-editor",".monaco-editor",".ace_editor",".ql-editor",".tiptap",".ProseMirror","[data-richclay-active]"],a=null,S=null,z=null,h=null;function F(e,t){if(h){if(h.inst===e)return h.cleanup;throw new Error("hyper-undo: another scope already owns the global Cmd+Z binding (bindKeys: true). Stop it first, or use bindKeys: false for additional scopes.")}let n=q(e,t);return h={inst:e,cleanup:n},n}function B(e){h&&h.inst===e&&(h.cleanup(),h=null)}var C={defaults:{shadowKeydownIn:A},start(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");if(a){if(S===t)return console.warn("[hyper-undo] start() called again on existing singleton; ignoring new options"),a;throw new Error("hyper-undo: start() called with a different scope while the singleton is already started. Use undo.create({ scope }) for additional scopes, or call undo.stop() first.")}let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A};return a=N(n),a.start(),S=t,e.bindKeys!==!1&&(z=F(a,()=>C.stop())),a},stop(){a&&(B(a),z=null,a.stop(),a=null,S=null)},create(e={}){let t=e.scope||(typeof document<"u"?document.body:null);if(!t)throw new Error("hyper-undo: no scope (need document.body or explicit { scope })");let n={...e,scope:t,shadowKeydownIn:e.shadowKeydownIn||A},r=N(n),o=null,i={_config:r._config,commit:r.commit,commitCaptured:r.commitCaptured,discardCaptured:r.discardCaptured,flush:r.flush,record:r.record,recordValue:r.recordValue,undo:r.undo,redo:r.redo,clear:r.clear,pause:r.pause,resume:r.resume,on:r.on,off:r.off,get canUndo(){return r.canUndo},get canRedo(){return r.canRedo},get isPaused(){return r.isPaused},get history(){return r.history},get scope(){return r.scope},start(){e.bindKeys&&(o=F(r,()=>i.stop())),r.start()},stop(){B(r),o=null,r.stop()}};return i},commit(e,t){return a?a.commit(e,t):void 0},commitCaptured(e){return a?a.commitCaptured(e):void 0},discardCaptured(){return a?a.discardCaptured():void 0},record(e){return a?a.record(e):void 0},recordValue(e,t){return a?a.recordValue(e,t):void 0},undo(){return a?a.undo():void 0},redo(){return a?a.redo():void 0},clear(){return a?a.clear():void 0},pause(){return a?a.pause():void 0},resume(){return a?a.resume():void 0},flush(){return a?a.flush():void 0},on(e,t){return a?a.on(e,t):void 0},off(e,t){return a?a.off(e,t):void 0},get canUndo(){return a?a.canUndo:!1},get canRedo(){return a?a.canRedo:!1},get history(){return a?a.history:[]},get isPaused(){return a?a.isPaused:!1}};var pe=C;return ae(me);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyper-undo",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "DOM-state undo/redo via MutationObserver inverse-op replay. Standalone or paired with hyperclayjs.",
5
5
  "keywords": [
6
6
  "undo",
@@ -14,7 +14,7 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/panphora/hyper-undo/issues"
16
16
  },
17
- "license": "MIT",
17
+ "license": "MIT-0",
18
18
  "repository": {
19
19
  "type": "git",
20
20
  "url": "git+https://github.com/panphora/hyper-undo.git"
package/src/index.js CHANGED
@@ -9,6 +9,14 @@ const DEFAULT_SHADOW_LIST = [
9
9
  '.ql-editor',
10
10
  '.tiptap',
11
11
  '.ProseMirror',
12
+ // richclay marks its live region with this and stamps no-undo on it, so the
13
+ // page-level stack holds no record of what was typed there. Without the
14
+ // bypass, Cmd+Z inside a richclay editor never reached Squire: it either did
15
+ // nothing or reverted an unrelated earlier edit elsewhere on the page.
16
+ // richclay has always intended this; see the comment on setupEditorAttributes
17
+ // in richclay/src/richclay.js, which says it marks the region so hyper-undo
18
+ // "defers to Squire instead of fighting it".
19
+ '[data-richclay-active]',
12
20
  ]
13
21
 
14
22
  let singleton = null // the Scope instance returned by createScope