phlex-reactive 0.16.2 → 0.16.4

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.
@@ -7,10 +7,10 @@
7
7
  //
8
8
  // data-reactive-frame the coordinate space: its padding box
9
9
  // data-reactive-box='{"x":"crop[x]",…}' a box and the names of its fields
10
- // -min / -ratio / -aspect / -action / -busy / -messages
10
+ // -min / -ratio / -aspect / -scale / -action / -busy / -messages
11
11
  // data-reactive-box-handle="se" an edge or corner (inside the box)
12
12
  // data-reactive-point='{"x":…,"y":…}' a point (its centre)
13
- // -within / -action / -busy / -messages
13
+ // -within / -scale / -action / -busy / -messages
14
14
  //
15
15
  // and what it sets while a pointer drag is on, for the app's CSS:
16
16
  //
@@ -20,12 +20,14 @@
20
20
  // Where an element is lives in its own inline style — left/top/width/height
21
21
  // in percent, rendered by the server and written here — so a morph that
22
22
  // renders new values moves it, and nothing is cached between drags. Each
23
- // field's .value is the same number as a fraction, rounded to 4 decimals and
24
- // written as set_value writes (core.setValue: a bubbling `input` when it
25
- // moved), at most once a frame while a pointer drags; one `change` follows on
26
- // release. A box's x/y/width/height are fractions of the frame; a point's x/y
27
- // of the element it sits in that matches its -within selector, else of the
28
- // frame.
23
+ // field's .value is the same number as a fraction, rounded to 4 decimals —
24
+ // or, with -scale (issue #428), that fraction times the scale (100: percent,
25
+ // 2 decimals) — written as set_value writes (core.setValue: a bubbling
26
+ // `input` when it moved), at most once a frame while a pointer drags; one
27
+ // `change` follows on release. A box's x/y/width/height are fractions of the
28
+ // frame; a point's x/y of the element it sits in that matches its -within
29
+ // selector, else of the frame. Everything else — the style, the action's
30
+ // params, the words — stays in fractions.
29
31
  //
30
32
  // Input is pointer events: mouse, pen and touch behave the same (the helpers
31
33
  // render touch-action: none, so a finger drag does not scroll), and a drag
@@ -98,6 +100,16 @@ const clamp = (value, low, high) => Math.min(Math.max(value, low), high)
98
100
 
99
101
  export const round = (value) => Math.round(value * 1e4) / 1e4
100
102
 
103
+ // What a field on `scale` holds for a fraction: its 4 decimals times the
104
+ // scale, to 15 significant digits — every digit it has, without the float
105
+ // noise: 0.1235 is "0.1235", "12.35" on 100 (not 12.350000000000001), "3.0875"
106
+ // on 25. (A product that rounds up past the largest number stays as it is.)
107
+ export function scaled(value, scale = 1) {
108
+ const exact = round(value) * scale
109
+ const clean = Number(exact.toPrecision(15))
110
+ return String(Number.isFinite(clean) ? clean : exact)
111
+ }
112
+
101
113
  // A position as the fields will hold it: each number rounded to 4 decimals,
102
114
  // a box's x/y then pulled in so it still ends inside the frame (rounded
103
115
  // apart, 0.45545 + 0.54455 is 0.4555 + 0.5446 = 1.0001).
@@ -179,6 +191,7 @@ const json = (value, fallback) => {
179
191
  }
180
192
  }
181
193
  const kind = (el) => (el.matches(POINT) ? "point" : "box")
194
+ const scaleOf = (el) => Number(attr(el, `${kind(el)}-scale`)) || 1
182
195
  const keysOf = (el) => (kind(el) === "point" ? ["x", "y"] : ["x", "y", "width", "height"])
183
196
 
184
197
  function read(el) {
@@ -256,16 +269,26 @@ function begin(core, root, grip, keyboard) {
256
269
  value,
257
270
  ratio: point ? 0 : ratioOf(el, frame, root, core),
258
271
  min: Number(attr(el, "box-min")) || 0,
272
+ scale: scaleOf(el),
259
273
  fields,
260
274
  before: Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.value])),
261
275
  }
262
276
  }
263
277
 
278
+ // Write a field the fraction on its scale, as set_value writes — unless it
279
+ // already holds that number, within half a step of the fraction's 4th
280
+ // decimal on its scale ("50.0", the 50 a server rendered for 0.5 × 100, is
281
+ // not a move: no `input`, and no `change` on release).
282
+ function put(core, field, value, scale) {
283
+ const text = scaled(value, scale)
284
+ if (field.value === "" || !(Math.abs(field.value - text) < scale / 2e4)) core.setValue(field, text)
285
+ }
286
+
264
287
  // Write the fields now (set_value's write: `input` on each that moved).
265
288
  function flush(record) {
266
289
  cancelAnimationFrame(record.writing)
267
290
  record.writing = 0
268
- for (const [key, field] of Object.entries(record.fields)) record.core.setValue(field, String(round(record.value[key])))
291
+ for (const [key, field] of Object.entries(record.fields)) put(record.core, field, record.value[key], record.scale)
269
292
  }
270
293
 
271
294
  // --- Pointer --------------------------------------------------------------------------
@@ -515,8 +538,7 @@ function holdMorphs() {
515
538
  const core = cores.get(root)
516
539
  if (!core?.owns(el)) return
517
540
  const value = read(el)
518
- for (const [key, field] of Object.entries(fieldsOf(el, root, core))) {
519
- if (round(Number(field.value)) !== round(value[key])) core.setValue(field, String(round(value[key])))
520
- }
541
+ const scale = scaleOf(el)
542
+ for (const [key, field] of Object.entries(fieldsOf(el, root, core))) put(core, field, value[key], scale)
521
543
  })
522
544
  }
@@ -1,4 +1,4 @@
1
- var j={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]},q={x:"left",y:"top",width:"width",height:"height"},I=new WeakMap,G=new WeakMap,i=null,y,H,B=!1;function ft(t,e){let n=t.element;G.set(n,e),Q(),lt();let c=[["pointerdown",(o)=>st(o,e,n)],["keydown",(o)=>ut(o,e,n)],["keyup",(o)=>i?.keyboard&&o.target===i.grip&&j[o.key]&&N()],["focusout",(o)=>i?.keyboard&&o.target===i.grip&&N()],["input",(o)=>X(o,e,n)],["change",(o)=>X(o,e,n)]];for(let[o,s]of c)n.addEventListener(o,s);I.set(t,()=>{for(let[o,s]of c)n.removeEventListener(o,s)})}function ht(t){if(I.get(t)?.(),I.delete(t),G.delete(t.element),i?.root===t.element)A(i)}var m=(t,e,n)=>Math.min(Math.max(t,e),n),d=(t)=>Math.round(t*1e4)/1e4;function S(t){let e={x:d(t.x),y:d(t.y)};if(t.width!=null)e.width=d(t.width),e.height=d(t.height),e.x=Math.min(e.x,d(1-e.width)),e.y=Math.min(e.y,d(1-e.height));return e}function V(t,e,n){return{x:m(t.x+e,0,1-t.width),y:m(t.y+n,0,1-t.height),width:t.width,height:t.height}}function W(t,e,n){return{x:m(t.x+e,0,1),y:m(t.y+n,0,1)}}function Y(t,e,n,c,o,s){let[r,a,l,f]=["n","e","s","w"].map((R)=>e.includes(R)),x=t.x+t.width,k=t.y+t.height,O=t.x+t.width/2,E=t.y+t.height/2,v=a?1-t.x:f?x:s?2*Math.min(O,1-O):1,u=l?1-t.y:r?k:s?2*Math.min(E,1-E):1,h=t.width+(a?n:f?-n:0),g=t.height+(l?c:r?-c:0);if(s){let R=(a||f)&&(!(r||l)||Math.abs(h-t.width)>=Math.abs(g-t.height)*s),P=Math.min(v,u*s);h=m(R?h:g*s,Math.min(Math.max(o,o*s),P),P),g=h/s}else{if(a||f)h=m(h,Math.min(o,v),v);if(r||l)g=m(g,Math.min(o,u),u)}return{x:f?x-h:a?t.x:O-h/2,y:r?k-g:l?t.y:E-g/2,width:h,height:g}}function et(t,e,n){let c=Math.min(1,e),o=m(Math.sqrt(t.width*t.height*e),Math.min(Math.max(n,n*e),c),c),s=o/e;return{x:m(t.x+t.width/2-o/2,0,1-o),y:m(t.y+t.height/2-s/2,0,1-s),width:o,height:s}}function nt(t){let[e,n=1,c]=String(t??"").split(":").map(Number),o=c===void 0?e/n:0;return o>0&&Number.isFinite(o)?o:0}var p=(t,e)=>t?.getAttribute?.(`data-reactive-${e}`),T=(t,e)=>{try{return t==null?e:JSON.parse(t)}catch{return e}},w=(t)=>t.matches("[data-reactive-point]")?"point":"box",b=(t)=>w(t)==="point"?["x","y"]:["x","y","width","height"];function z(t){let e={};for(let n of b(t))e[n]=(parseFloat(t.style[q[n]])||0)/100;return e}function D(t,e){for(let n of b(t))t.style[q[n]]=`${Math.round(e[n]*1e4)/100}%`}function F(t){let e=t.getBoundingClientRect();return{left:e.left+(t.clientLeft||0),top:e.top+(t.clientTop||0),width:t.clientWidth||e.width,height:t.clientHeight||e.height}}function ot(t,e){try{return t.parentElement.closest(p(t,"point-within")||"[data-reactive-frame]")??e}catch{return e}}function J(t,e,n){return[...t.querySelectorAll("[name]")].filter((c)=>c.name===n&&e.owns(c))}function K(t,e,n){let c=T(p(t,w(t)),{}),o={};for(let s of b(t)){let[r]=J(e,n,c[s]);if(r)o[s]=r}return o}function it(t,e,n,c){let o=p(t,"box-aspect"),s=o&&J(n,c,o).find((f)=>!/^(radio|checkbox)$/.test(f.type)||f.checked),r=o?nt(s?.value):Number(p(t,"box-ratio"))||0,{width:a,height:l}=F(e);return r&&a&&l?r*l/a:0}function C(t,e,n,c){let o=n.matches("[data-reactive-box-handle]")?n.closest("[data-reactive-box]"):n,s=o?.closest("[data-reactive-frame]");if(!s)return null;let r=z(o),a=K(o,e,t),l=w(o)==="point";return{core:t,root:e,grip:n,el:o,frame:s,keyboard:c,bounds:l?ot(o,s):s,edge:p(n,"box-handle"),start:r,value:r,ratio:l?0:it(o,s,e,t),min:Number(p(o,"box-min"))||0,fields:a,before:Object.fromEntries(Object.entries(a).map(([f,x])=>[f,x.value]))}}function M(t){cancelAnimationFrame(t.writing),t.writing=0;for(let[e,n]of Object.entries(t.fields))t.core.setValue(n,String(d(t.value[e])))}function st(t,e,n){if(U(),i||t.button>0||t.isPrimary===!1||t.target.closest?.("input,textarea,select,[contenteditable]"))return;let c=t.target.closest?.("[data-reactive-box-handle],[data-reactive-point],[data-reactive-box]"),o=c&&e.owns(c)&&C(e,n,c,!1);if(!o)return;let{clientX:s,clientY:r}=t,a=F(o.bounds);o.p0=[(s-a.left)/a.width,(r-a.top)/a.height];let l=(u)=>u.pointerId===t.pointerId,f=(u)=>{if(!l(u)||o.over)return;if(i)return L(u.clientX,u.clientY);if(Math.abs(u.clientX-s)<=4&&Math.abs(u.clientY-r)<=4)return;rt(o),L(u.clientX,u.clientY)},x=(u)=>{if(!l(u))return;if(v(),i)ct(),N()},k=(u)=>{if(u.type==="pointercancel"&&!l(u))return;if(v(),i)tt()},E=[["pointermove",f],["pointerup",x],["pointercancel",k],["keydown",(u)=>u.key==="Escape"&&i&&(u.preventDefault(),k(u))]];for(let[u,h]of E)document.addEventListener(u,h);function v(){for(let[u,h]of E)document.removeEventListener(u,h)}}function ct(){let t=(e)=>(e.preventDefault(),e.stopPropagation());window.addEventListener("click",t,{capture:!0,once:!0}),setTimeout(()=>window.removeEventListener("click",t,{capture:!0}))}function rt(t){i=t,t.grip.setAttribute("data-reactive-dragging",""),t.frame.setAttribute("data-reactive-frame-active",""),t.select=document.documentElement.style.userSelect,document.documentElement.style.userSelect="none"}function L(t,e){let n=i;if(!n.el.isConnected)return A(n);n.x=t,n.y=e;let c=F(n.bounds),o=(t-c.left)/c.width-n.p0[0],s=(e-c.top)/c.height-n.p0[1],{start:r,edge:a}=n;n.value=S(w(n.el)==="point"?W(r,o,s):a?Y(r,a,o,s,n.min,n.ratio):V(r,o,s)),D(n.el,n.value),n.writing||=requestAnimationFrame(()=>M(n)),at(e)}function at(t){if(i.wy=t<40?-12:t>window.innerHeight-40?12:0,i.wy&&!i.raf){let e=()=>{if(!i?.wy)return i&&(i.raf=0);if(window.scrollBy(0,i.wy),L(i.x,i.y),i)i.raf=requestAnimationFrame(e)};i.raf=requestAnimationFrame(e)}}function U(){if(i&&!i.el.isConnected)A(i)}function ut(t,e,n){U();let c=t.target;if(!c?.matches?.("[data-reactive-box-handle],[data-reactive-point],[data-reactive-box]")||!e.owns(c)||i&&(!i.keyboard||i.grip!==c))return;if(t.key==="Escape")return i&&(t.preventDefault(),tt());let o=j[t.key];if(!o)return;if(t.preventDefault(),i??=C(e,n,c,!0),!i)return;let[s,r]=o.map((f)=>f*(t.shiftKey?0.1:0.01)),a=t.altKey&&w(i.el)==="box",l=i.value;i.value=S(w(i.el)==="point"?W(l,s,r):a?Y(l,i.edge??"se",s,r,i.min,i.ratio):V(l,s,r)),D(i.el,i.value),M(i),Z(i,a?"resized":"moved")}function Q(){if(!y?.isConnected)y=document.createElement("div"),y.setAttribute("aria-live","polite"),y.setAttribute("data-reactive-drag-live",""),y.style.cssText="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap",document.body.append(y);return y}function Z(t,e){let n=T(p(t.el,`${w(t.el)}-messages`),{})[e]??"";for(let o of b(t.el))n=n.replaceAll(`{${o}}`,Math.round(t.value[o]*100));let c=Q();c.textContent="",cancelAnimationFrame(H),H=requestAnimationFrame(()=>c.textContent=n)}function X(t,e,n){let c=t.target?.name;if(!c)return;for(let o of n.querySelectorAll("[data-reactive-box-aspect]")){if(p(o,"box-aspect")!==c||o===i?.el||!e.owns(o))continue;let s=C(e,n,o,!0),{ratio:r,start:a}=s??{};if(!r||Math.abs(a.width/a.height/r-1)<0.005)continue;s.value=S(et(a,r,s.min)),D(o,s.value),M(s),_(s)}}function N(){let t=i;if(!t.el.isConnected)return A(t);M(t),A(t),_(t)}function _(t){for(let[o,s]of Object.entries(t.fields))if(s.value!==t.before[o])s.dispatchEvent(new Event("change",{bubbles:!0}));let e=w(t.el),n=p(t.el,`${e}-action`),c={};for(let o of b(t.el))c[o]=d(t.value[o]);if(!n||b(t.el).every((o)=>c[o]===d(t.start[o])))return;t.core.proceed(t.el,n,JSON.stringify(c),T(p(t.el,`${e}-busy`)))}function tt(){let t=i;if(t.value=t.start,D(t.el,t.start),M(t),t.keyboard)Z(t,"cancelled");A(t)}function A(t){if(i=null,t.over=!0,cancelAnimationFrame(t.raf),cancelAnimationFrame(t.writing),t.grip.removeAttribute("data-reactive-dragging"),t.frame.removeAttribute("data-reactive-frame-active"),!t.keyboard)document.documentElement.style.userSelect=t.select??""}function lt(){if(B)return;B=!0,document.addEventListener("turbo:before-morph-attribute",(t)=>{let e=t.target,n=t.detail?.attributeName;if(!i)return;if(e===i.el&&n==="style"||e===i.grip&&n==="data-reactive-dragging"||e===i.frame&&n==="data-reactive-frame-active"||n==="value"&&Object.values(i.fields).includes(e))t.preventDefault()}),document.addEventListener("turbo:morph-element",(t)=>{let e=t.target;if(e===i?.el||!e.matches?.("[data-reactive-box],[data-reactive-point]"))return;let n=e.closest('[data-controller~="reactive"]'),c=G.get(n);if(!c?.owns(e))return;let o=z(e);for(let[s,r]of Object.entries(K(e,n,c)))if(d(Number(r.value))!==d(o[s]))c.setValue(r,String(d(o[s])))})}export{ft as connect,ht as disconnect,et as fitBox,V as moveBox,W as movePoint,nt as parseRatio,Y as resizeBox,d as round,S as snap};
1
+ var j={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]},q={x:"left",y:"top",width:"width",height:"height"},N=new WeakMap,G=new WeakMap,i=null,y,H,B=!1;function mt(t,e){let n=t.element;G.set(n,e),_(),dt();let s=[["pointerdown",(o)=>at(o,e,n)],["keydown",(o)=>ht(o,e,n)],["keyup",(o)=>i?.keyboard&&o.target===i.grip&&j[o.key]&&L()],["focusout",(o)=>i?.keyboard&&o.target===i.grip&&L()],["input",(o)=>X(o,e,n)],["change",(o)=>X(o,e,n)]];for(let[o,c]of s)n.addEventListener(o,c);N.set(t,()=>{for(let[o,c]of s)n.removeEventListener(o,c)})}function pt(t){if(N.get(t)?.(),N.delete(t),G.delete(t.element),i?.root===t.element)A(i)}var m=(t,e,n)=>Math.min(Math.max(t,e),n),p=(t)=>Math.round(t*1e4)/1e4;function ot(t,e=1){let n=p(t)*e,s=Number(n.toPrecision(15));return String(Number.isFinite(s)?s:n)}function S(t){let e={x:p(t.x),y:p(t.y)};if(t.width!=null)e.width=p(t.width),e.height=p(t.height),e.x=Math.min(e.x,p(1-e.width)),e.y=Math.min(e.y,p(1-e.height));return e}function W(t,e,n){return{x:m(t.x+e,0,1-t.width),y:m(t.y+n,0,1-t.height),width:t.width,height:t.height}}function V(t,e,n){return{x:m(t.x+e,0,1),y:m(t.y+n,0,1)}}function Y(t,e,n,s,o,c){let[r,a,l,f]=["n","e","s","w"].map((R)=>e.includes(R)),x=t.x+t.width,M=t.y+t.height,O=t.x+t.width/2,v=t.y+t.height/2,E=a?1-t.x:f?x:c?2*Math.min(O,1-O):1,u=l?1-t.y:r?M:c?2*Math.min(v,1-v):1,h=t.width+(a?n:f?-n:0),w=t.height+(l?s:r?-s:0);if(c){let R=(a||f)&&(!(r||l)||Math.abs(h-t.width)>=Math.abs(w-t.height)*c),C=Math.min(E,u*c);h=m(R?h:w*c,Math.min(Math.max(o,o*c),C),C),w=h/c}else{if(a||f)h=m(h,Math.min(o,E),E);if(r||l)w=m(w,Math.min(o,u),u)}return{x:f?x-h:a?t.x:O-h/2,y:r?M-w:l?t.y:v-w/2,width:h,height:w}}function it(t,e,n){let s=Math.min(1,e),o=m(Math.sqrt(t.width*t.height*e),Math.min(Math.max(n,n*e),s),s),c=o/e;return{x:m(t.x+t.width/2-o/2,0,1-o),y:m(t.y+t.height/2-c/2,0,1-c),width:o,height:c}}function st(t){let[e,n=1,s]=String(t??"").split(":").map(Number),o=s===void 0?e/n:0;return o>0&&Number.isFinite(o)?o:0}var d=(t,e)=>t?.getAttribute?.(`data-reactive-${e}`),T=(t,e)=>{try{return t==null?e:JSON.parse(t)}catch{return e}},g=(t)=>t.matches("[data-reactive-point]")?"point":"box",z=(t)=>Number(d(t,`${g(t)}-scale`))||1,b=(t)=>g(t)==="point"?["x","y"]:["x","y","width","height"];function J(t){let e={};for(let n of b(t))e[n]=(parseFloat(t.style[q[n]])||0)/100;return e}function D(t,e){for(let n of b(t))t.style[q[n]]=`${Math.round(e[n]*1e4)/100}%`}function F(t){let e=t.getBoundingClientRect();return{left:e.left+(t.clientLeft||0),top:e.top+(t.clientTop||0),width:t.clientWidth||e.width,height:t.clientHeight||e.height}}function ct(t,e){try{return t.parentElement.closest(d(t,"point-within")||"[data-reactive-frame]")??e}catch{return e}}function K(t,e,n){return[...t.querySelectorAll("[name]")].filter((s)=>s.name===n&&e.owns(s))}function U(t,e,n){let s=T(d(t,g(t)),{}),o={};for(let c of b(t)){let[r]=K(e,n,s[c]);if(r)o[c]=r}return o}function rt(t,e,n,s){let o=d(t,"box-aspect"),c=o&&K(n,s,o).find((f)=>!/^(radio|checkbox)$/.test(f.type)||f.checked),r=o?st(c?.value):Number(d(t,"box-ratio"))||0,{width:a,height:l}=F(e);return r&&a&&l?r*l/a:0}function P(t,e,n,s){let o=n.matches("[data-reactive-box-handle]")?n.closest("[data-reactive-box]"):n,c=o?.closest("[data-reactive-frame]");if(!c)return null;let r=J(o),a=U(o,e,t),l=g(o)==="point";return{core:t,root:e,grip:n,el:o,frame:c,keyboard:s,bounds:l?ct(o,c):c,edge:d(n,"box-handle"),start:r,value:r,ratio:l?0:rt(o,c,e,t),min:Number(d(o,"box-min"))||0,scale:z(o),fields:a,before:Object.fromEntries(Object.entries(a).map(([f,x])=>[f,x.value]))}}function Q(t,e,n,s){let o=ot(n,s);if(e.value===""||!(Math.abs(e.value-o)<s/20000))t.setValue(e,o)}function k(t){cancelAnimationFrame(t.writing),t.writing=0;for(let[e,n]of Object.entries(t.fields))Q(t.core,n,t.value[e],t.scale)}function at(t,e,n){if(Z(),i||t.button>0||t.isPrimary===!1||t.target.closest?.("input,textarea,select,[contenteditable]"))return;let s=t.target.closest?.("[data-reactive-box-handle],[data-reactive-point],[data-reactive-box]"),o=s&&e.owns(s)&&P(e,n,s,!1);if(!o)return;let{clientX:c,clientY:r}=t,a=F(o.bounds);o.p0=[(c-a.left)/a.width,(r-a.top)/a.height];let l=(u)=>u.pointerId===t.pointerId,f=(u)=>{if(!l(u)||o.over)return;if(i)return I(u.clientX,u.clientY);if(Math.abs(u.clientX-c)<=4&&Math.abs(u.clientY-r)<=4)return;lt(o),I(u.clientX,u.clientY)},x=(u)=>{if(!l(u))return;if(E(),i)ut(),L()},M=(u)=>{if(u.type==="pointercancel"&&!l(u))return;if(E(),i)nt()},v=[["pointermove",f],["pointerup",x],["pointercancel",M],["keydown",(u)=>u.key==="Escape"&&i&&(u.preventDefault(),M(u))]];for(let[u,h]of v)document.addEventListener(u,h);function E(){for(let[u,h]of v)document.removeEventListener(u,h)}}function ut(){let t=(e)=>(e.preventDefault(),e.stopPropagation());window.addEventListener("click",t,{capture:!0,once:!0}),setTimeout(()=>window.removeEventListener("click",t,{capture:!0}))}function lt(t){i=t,t.grip.setAttribute("data-reactive-dragging",""),t.frame.setAttribute("data-reactive-frame-active",""),t.select=document.documentElement.style.userSelect,document.documentElement.style.userSelect="none"}function I(t,e){let n=i;if(!n.el.isConnected)return A(n);n.x=t,n.y=e;let s=F(n.bounds),o=(t-s.left)/s.width-n.p0[0],c=(e-s.top)/s.height-n.p0[1],{start:r,edge:a}=n;n.value=S(g(n.el)==="point"?V(r,o,c):a?Y(r,a,o,c,n.min,n.ratio):W(r,o,c)),D(n.el,n.value),n.writing||=requestAnimationFrame(()=>k(n)),ft(e)}function ft(t){if(i.wy=t<40?-12:t>window.innerHeight-40?12:0,i.wy&&!i.raf){let e=()=>{if(!i?.wy)return i&&(i.raf=0);if(window.scrollBy(0,i.wy),I(i.x,i.y),i)i.raf=requestAnimationFrame(e)};i.raf=requestAnimationFrame(e)}}function Z(){if(i&&!i.el.isConnected)A(i)}function ht(t,e,n){Z();let s=t.target;if(!s?.matches?.("[data-reactive-box-handle],[data-reactive-point],[data-reactive-box]")||!e.owns(s)||i&&(!i.keyboard||i.grip!==s))return;if(t.key==="Escape")return i&&(t.preventDefault(),nt());let o=j[t.key];if(!o)return;if(t.preventDefault(),i??=P(e,n,s,!0),!i)return;let[c,r]=o.map((f)=>f*(t.shiftKey?0.1:0.01)),a=t.altKey&&g(i.el)==="box",l=i.value;i.value=S(g(i.el)==="point"?V(l,c,r):a?Y(l,i.edge??"se",c,r,i.min,i.ratio):W(l,c,r)),D(i.el,i.value),k(i),tt(i,a?"resized":"moved")}function _(){if(!y?.isConnected)y=document.createElement("div"),y.setAttribute("aria-live","polite"),y.setAttribute("data-reactive-drag-live",""),y.style.cssText="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap",document.body.append(y);return y}function tt(t,e){let n=T(d(t.el,`${g(t.el)}-messages`),{})[e]??"";for(let o of b(t.el))n=n.replaceAll(`{${o}}`,Math.round(t.value[o]*100));let s=_();s.textContent="",cancelAnimationFrame(H),H=requestAnimationFrame(()=>s.textContent=n)}function X(t,e,n){let s=t.target?.name;if(!s)return;for(let o of n.querySelectorAll("[data-reactive-box-aspect]")){if(d(o,"box-aspect")!==s||o===i?.el||!e.owns(o))continue;let c=P(e,n,o,!0),{ratio:r,start:a}=c??{};if(!r||Math.abs(a.width/a.height/r-1)<0.005)continue;c.value=S(it(a,r,c.min)),D(o,c.value),k(c),et(c)}}function L(){let t=i;if(!t.el.isConnected)return A(t);k(t),A(t),et(t)}function et(t){for(let[o,c]of Object.entries(t.fields))if(c.value!==t.before[o])c.dispatchEvent(new Event("change",{bubbles:!0}));let e=g(t.el),n=d(t.el,`${e}-action`),s={};for(let o of b(t.el))s[o]=p(t.value[o]);if(!n||b(t.el).every((o)=>s[o]===p(t.start[o])))return;t.core.proceed(t.el,n,JSON.stringify(s),T(d(t.el,`${e}-busy`)))}function nt(){let t=i;if(t.value=t.start,D(t.el,t.start),k(t),t.keyboard)tt(t,"cancelled");A(t)}function A(t){if(i=null,t.over=!0,cancelAnimationFrame(t.raf),cancelAnimationFrame(t.writing),t.grip.removeAttribute("data-reactive-dragging"),t.frame.removeAttribute("data-reactive-frame-active"),!t.keyboard)document.documentElement.style.userSelect=t.select??""}function dt(){if(B)return;B=!0,document.addEventListener("turbo:before-morph-attribute",(t)=>{let e=t.target,n=t.detail?.attributeName;if(!i)return;if(e===i.el&&n==="style"||e===i.grip&&n==="data-reactive-dragging"||e===i.frame&&n==="data-reactive-frame-active"||n==="value"&&Object.values(i.fields).includes(e))t.preventDefault()}),document.addEventListener("turbo:morph-element",(t)=>{let e=t.target;if(e===i?.el||!e.matches?.("[data-reactive-box],[data-reactive-point]"))return;let n=e.closest('[data-controller~="reactive"]'),s=G.get(n);if(!s?.owns(e))return;let o=J(e),c=z(e);for(let[r,a]of Object.entries(U(e,n,s)))Q(s,a,o[r],c)})}export{mt as connect,pt as disconnect,it as fitBox,W as moveBox,V as movePoint,st as parseRatio,Y as resizeBox,p as round,ot as scaled,S as snap};
2
2
 
3
- //# debugId=94F6177CA2EF4E8764756E2164756E21
3
+ //# debugId=0D638B03EC2311D064756E2164756E21
4
4
  //# sourceMappingURL=drag.min.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["features/drag.js"],
4
4
  "sourcesContent": [
5
- "// phlex/reactive/features/drag — move and resize a box, or move a point,\n// inside a frame, with the result in fields as fractions of the frame (issue\n// #416). One of the feature modules the core imports on demand (issue #275);\n// in the default client it is part of the one file.\n//\n// The markup is the Ruby helpers' (Phlex::Reactive::Component::Drag):\n//\n// data-reactive-frame the coordinate space: its padding box\n// data-reactive-box='{\"x\":\"crop[x]\",…}' a box and the names of its fields\n// -min / -ratio / -aspect / -action / -busy / -messages\n// data-reactive-box-handle=\"se\" an edge or corner (inside the box)\n// data-reactive-point='{\"x\":…,\"y\":…}' a point (its centre)\n// -within / -action / -busy / -messages\n//\n// and what it sets while a pointer drag is on, for the app's CSS:\n//\n// data-reactive-frame-active on the frame\n// data-reactive-dragging on the pressed box, handle or point\n//\n// Where an element is lives in its own inline style — left/top/width/height\n// in percent, rendered by the server and written here — so a morph that\n// renders new values moves it, and nothing is cached between drags. Each\n// field's .value is the same number as a fraction, rounded to 4 decimals and\n// written as set_value writes (core.setValue: a bubbling `input` when it\n// moved), at most once a frame while a pointer drags; one `change` follows on\n// release. A box's x/y/width/height are fractions of the frame; a point's x/y\n// of the element it sits in that matches its -within selector, else of the\n// frame.\n//\n// Input is pointer events: mouse, pen and touch behave the same (the helpers\n// render touch-action: none, so a finger drag does not scroll), and a drag\n// starts after a 4 px move. The keyboard moves the focused box or point\n// directly — arrows 1%, Shift 10%, Alt resizes — and the key's release is\n// the release. A declared action is sent once per release through the\n// root's own pipeline (core.proceed).\n//\n// This module never imports the core: it reaches a controller through the\n// `core` handle (core.owns, core.proceed, core.setValue).\n\nconst FRAME = \"[data-reactive-frame]\"\nconst BOX = \"[data-reactive-box]\"\nconst POINT = \"[data-reactive-point]\"\nconst HANDLE = \"[data-reactive-box-handle]\"\nconst GRIP = `${HANDLE},${POINT},${BOX}`\nconst ROOT = '[data-controller~=\"reactive\"]'\nconst FIELD = \"input,textarea,select,[contenteditable]\"\nconst DRAGGING = \"data-reactive-dragging\"\nconst ACTIVE = \"data-reactive-frame-active\"\nconst THRESHOLD = 4 // px a press must move before it is a drag\nconst EDGE = 40 // px from the window's top or bottom where auto-scroll starts\nconst SPEED = 12 // px auto-scroll moves per frame\nconst ARROWS = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }\nconst STYLE = { x: \"left\", y: \"top\", width: \"width\", height: \"height\" }\n\n// controller -> the removals of what connect() added.\nconst wired = new WeakMap()\n// reactive root element -> its core handle, for a morph of a box in it.\nconst cores = new WeakMap()\n// The one drag on the page, by pointer or keyboard, or null.\nlet drag = null\n// The page's live region (re-created after a Turbo visit replaced <body>),\n// and the frame that will write its next words.\nlet live\nlet speaking\nlet morphs = false\n\nexport function connect(controller, core) {\n const root = controller.element\n cores.set(root, core)\n // Empty on the page before anything is announced (a screen reader often\n // ignores a region that arrives already holding words).\n liveRegion()\n holdMorphs()\n const on = [\n [\"pointerdown\", (event) => pointerDown(event, core, root)],\n [\"keydown\", (event) => keyDown(event, core, root)],\n [\"keyup\", (event) => drag?.keyboard && event.target === drag.grip && ARROWS[event.key] && release()],\n [\"focusout\", (event) => drag?.keyboard && event.target === drag.grip && release()],\n [\"input\", (event) => aspectChanged(event, core, root)],\n [\"change\", (event) => aspectChanged(event, core, root)],\n ]\n for (const [type, fn] of on) root.addEventListener(type, fn)\n wired.set(controller, () => {\n for (const [type, fn] of on) root.removeEventListener(type, fn)\n })\n}\n\nexport function disconnect(controller) {\n wired.get(controller)?.()\n wired.delete(controller)\n cores.delete(controller.element)\n // A root that leaves mid-drag (a replace, a navigation) ends its drag.\n if (drag?.root === controller.element) settle(drag)\n}\n\n// --- Geometry: fractions, 0..1 on each axis --------------------------------------\nconst clamp = (value, low, high) => Math.min(Math.max(value, low), high)\n\nexport const round = (value) => Math.round(value * 1e4) / 1e4\n\n// A position as the fields will hold it: each number rounded to 4 decimals,\n// a box's x/y then pulled in so it still ends inside the frame (rounded\n// apart, 0.45545 + 0.54455 is 0.4555 + 0.5446 = 1.0001).\nexport function snap(value) {\n const out = { x: round(value.x), y: round(value.y) }\n if (value.width != null) {\n out.width = round(value.width)\n out.height = round(value.height)\n out.x = Math.min(out.x, round(1 - out.width))\n out.y = Math.min(out.y, round(1 - out.height))\n }\n return out\n}\n\nexport function moveBox(b, dx, dy) {\n return { x: clamp(b.x + dx, 0, 1 - b.width), y: clamp(b.y + dy, 0, 1 - b.height), width: b.width, height: b.height }\n}\n\nexport function movePoint(p, dx, dy) {\n return { x: clamp(p.x + dx, 0, 1), y: clamp(p.y + dy, 0, 1) }\n}\n\n// Resize from `edge` (a handle: n, se, …) by (dx, dy), the opposite side\n// staying put, inside the frame and never below `min`. `ratio` is the locked\n// width over height IN FRACTIONS (0: free): on a corner the axis the pointer\n// moved more drives, on an edge the other axis grows around the box's centre.\nexport function resizeBox(b, edge, dx, dy, min, ratio) {\n const [n, e, s, w] = [\"n\", \"e\", \"s\", \"w\"].map((side) => edge.includes(side))\n const right = b.x + b.width\n const bottom = b.y + b.height\n const cx = b.x + b.width / 2\n const cy = b.y + b.height / 2\n // From the side that stays to the frame's edge (an axis no handle drives,\n // with a ratio: around its centre).\n const roomW = e ? 1 - b.x : w ? right : ratio ? 2 * Math.min(cx, 1 - cx) : 1\n const roomH = s ? 1 - b.y : n ? bottom : ratio ? 2 * Math.min(cy, 1 - cy) : 1\n let width = b.width + (e ? dx : w ? -dx : 0)\n let height = b.height + (s ? dy : n ? -dy : 0)\n if (ratio) {\n const byWidth = (e || w) && (!(n || s) || Math.abs(width - b.width) >= Math.abs(height - b.height) * ratio)\n const high = Math.min(roomW, roomH * ratio)\n width = clamp(byWidth ? width : height * ratio, Math.min(Math.max(min, min * ratio), high), high)\n height = width / ratio\n } else {\n if (e || w) width = clamp(width, Math.min(min, roomW), roomW)\n if (n || s) height = clamp(height, Math.min(min, roomH), roomH)\n }\n return { x: w ? right - width : e ? b.x : cx - width / 2, y: n ? bottom - height : s ? b.y : cy - height / 2, width, height }\n}\n\n// The box at a new `ratio` (fractions): the same area, centred where it was,\n// inside the frame and no smaller than `min`.\nexport function fitBox(b, ratio, min) {\n const high = Math.min(1, ratio)\n const width = clamp(Math.sqrt(b.width * b.height * ratio), Math.min(Math.max(min, min * ratio), high), high)\n const height = width / ratio\n return {\n x: clamp(b.x + b.width / 2 - width / 2, 0, 1 - width),\n y: clamp(b.y + b.height / 2 - height / 2, 0, 1 - height),\n width,\n height,\n }\n}\n\n// \"16:9\", \"1.5\" or \"\" (free): width over height, or 0 for free.\nexport function parseRatio(text) {\n const [width, height = 1, extra] = String(text ?? \"\").split(\":\").map(Number)\n const ratio = extra === undefined ? width / height : 0\n return ratio > 0 && Number.isFinite(ratio) ? ratio : 0\n}\n\n// --- Reading and writing an element ------------------------------------------------\nconst attr = (el, name) => el?.getAttribute?.(`data-reactive-${name}`)\nconst json = (value, fallback) => {\n try {\n return value == null ? fallback : JSON.parse(value)\n } catch {\n return fallback\n }\n}\nconst kind = (el) => (el.matches(POINT) ? \"point\" : \"box\")\nconst keysOf = (el) => (kind(el) === \"point\" ? [\"x\", \"y\"] : [\"x\", \"y\", \"width\", \"height\"])\n\nfunction read(el) {\n const value = {}\n for (const key of keysOf(el)) value[key] = (parseFloat(el.style[STYLE[key]]) || 0) / 100\n return value\n}\n\nfunction write(el, value) {\n for (const key of keysOf(el)) el.style[STYLE[key]] = `${Math.round(value[key] * 1e4) / 100}%`\n}\n\n// An element's padding box in viewport px: what CSS % on a child resolves against.\nfunction paddingBox(el) {\n const rect = el.getBoundingClientRect()\n return {\n left: rect.left + (el.clientLeft || 0),\n top: rect.top + (el.clientTop || 0),\n width: el.clientWidth || rect.width,\n height: el.clientHeight || rect.height,\n }\n}\n\n// A point's bounds: the element it sits in that matches -within, else its frame.\nfunction boundsOf(el, frame) {\n try {\n return el.parentElement.closest(attr(el, \"point-within\") || FRAME) ?? frame\n } catch {\n return frame // an invalid selector\n }\n}\n\n// The root's own fields of these names (a nested root's are its own, issue #15).\nfunction named(root, core, name) {\n return [...root.querySelectorAll(\"[name]\")].filter((field) => field.name === name && core.owns(field))\n}\n\nfunction fieldsOf(el, root, core) {\n const names = json(attr(el, kind(el)), {})\n const fields = {}\n for (const key of keysOf(el)) {\n const [field] = named(root, core, names[key])\n if (field) fields[key] = field\n }\n return fields\n}\n\n// The locked ratio in fractions (the pixel ratio over the frame's), or 0.\nfunction ratioOf(box, frame, root, core) {\n const name = attr(box, \"box-aspect\")\n const field = name && named(root, core, name).find((f) => !/^(radio|checkbox)$/.test(f.type) || f.checked)\n const ratio = name ? parseRatio(field?.value) : Number(attr(box, \"box-ratio\")) || 0\n const { width, height } = paddingBox(frame)\n return ratio && width && height ? (ratio * height) / width : 0\n}\n\n// What a drag (or a key, or a re-fit) works on, read off the element now.\nfunction begin(core, root, grip, keyboard) {\n const el = grip.matches(HANDLE) ? grip.closest(BOX) : grip\n const frame = el?.closest(FRAME)\n if (!frame) return null\n const value = read(el)\n const fields = fieldsOf(el, root, core)\n const point = kind(el) === \"point\"\n return {\n core,\n root,\n grip,\n el,\n frame,\n keyboard,\n bounds: point ? boundsOf(el, frame) : frame,\n edge: attr(grip, \"box-handle\"),\n start: value,\n value,\n ratio: point ? 0 : ratioOf(el, frame, root, core),\n min: Number(attr(el, \"box-min\")) || 0,\n fields,\n before: Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.value])),\n }\n}\n\n// Write the fields now (set_value's write: `input` on each that moved).\nfunction flush(record) {\n cancelAnimationFrame(record.writing)\n record.writing = 0\n for (const [key, field] of Object.entries(record.fields)) record.core.setValue(field, String(round(record.value[key])))\n}\n\n// --- Pointer --------------------------------------------------------------------------\nfunction pointerDown(event, core, root) {\n forgetRemoved()\n if (drag || event.button > 0 || event.isPrimary === false || event.target.closest?.(FIELD)) return\n const grip = event.target.closest?.(GRIP)\n const record = grip && core.owns(grip) && begin(core, root, grip, false)\n if (!record) return\n const x0 = event.clientX\n const y0 = event.clientY\n const bounds = paddingBox(record.bounds)\n record.p0 = [(x0 - bounds.left) / bounds.width, (y0 - bounds.top) / bounds.height]\n // Only the pointer that pressed drives the drag: a second finger is not it.\n const mine = (e) => e.pointerId === event.pointerId\n const move = (e) => {\n // (A drag that ended before the release — its element left — stays over.)\n if (!mine(e) || record.over) return\n if (drag) return follow(e.clientX, e.clientY)\n if (Math.abs(e.clientX - x0) <= THRESHOLD && Math.abs(e.clientY - y0) <= THRESHOLD) return\n start(record)\n follow(e.clientX, e.clientY)\n }\n const up = (e) => {\n if (!mine(e)) return\n end()\n if (drag) suppressClick(), release()\n }\n const abort = (e) => {\n if (e.type === \"pointercancel\" && !mine(e)) return\n end()\n if (drag) cancel()\n }\n const escape = (e) => e.key === \"Escape\" && drag && (e.preventDefault(), abort(e))\n const on = [\n [\"pointermove\", move],\n [\"pointerup\", up],\n [\"pointercancel\", abort],\n [\"keydown\", escape],\n ]\n for (const [type, fn] of on) document.addEventListener(type, fn)\n function end() {\n for (const [type, fn] of on) document.removeEventListener(type, fn)\n }\n}\n\n// The click a pointer drag ends with is not one.\nfunction suppressClick() {\n const stop = (event) => (event.preventDefault(), event.stopPropagation())\n window.addEventListener(\"click\", stop, { capture: true, once: true })\n setTimeout(() => window.removeEventListener(\"click\", stop, { capture: true }))\n}\n\nfunction start(record) {\n drag = record\n record.grip.setAttribute(DRAGGING, \"\")\n record.frame.setAttribute(ACTIVE, \"\")\n record.select = document.documentElement.style.userSelect\n document.documentElement.style.userSelect = \"none\"\n}\n\n// The pointer is at (x, y): the element goes where the pointer's move since\n// the press puts it, measured in its bounds as they are NOW (a scroll moved them).\nfunction follow(x, y) {\n const record = drag\n // A morph or a stream took the element away: the drag is over.\n if (!record.el.isConnected) return settle(record)\n record.x = x\n record.y = y\n const bounds = paddingBox(record.bounds)\n const dx = (x - bounds.left) / bounds.width - record.p0[0]\n const dy = (y - bounds.top) / bounds.height - record.p0[1]\n const { start: from, edge } = record\n record.value = snap(\n kind(record.el) === \"point\"\n ? movePoint(from, dx, dy)\n : edge\n ? resizeBox(from, edge, dx, dy, record.min, record.ratio)\n : moveBox(from, dx, dy),\n )\n write(record.el, record.value)\n record.writing ||= requestAnimationFrame(() => flush(record))\n scroll(y)\n}\n\n// Near the window's top or bottom edge, scroll one step a frame until the\n// pointer leaves the edge (a frame taller than the window).\nfunction scroll(y) {\n drag.wy = y < EDGE ? -SPEED : y > window.innerHeight - EDGE ? SPEED : 0\n if (drag.wy && !drag.raf) {\n const step = () => {\n if (!drag?.wy) return drag && (drag.raf = 0)\n window.scrollBy(0, drag.wy)\n follow(drag.x, drag.y)\n if (drag) drag.raf = requestAnimationFrame(step)\n }\n drag.raf = requestAnimationFrame(step)\n }\n}\n\n// A drag whose element left the page (a stream replaced it while an arrow\n// was held: no keyup or focusout reaches it) is over, not in the way.\nfunction forgetRemoved() {\n if (drag && !drag.el.isConnected) settle(drag)\n}\n\n// --- Keyboard --------------------------------------------------------------------------\n// A focused box, handle or point: the arrows move it (Shift: ten times as\n// far), Alt+arrows resize a box from the focused handle or the bottom-right\n// corner, Escape puts it back. The key's release ends the step (`change`, the\n// action); each step is announced in the element's own words.\nfunction keyDown(event, core, root) {\n forgetRemoved()\n const grip = event.target\n // (A pointer drag, or a key held on another element, owns the drag.)\n if (!grip?.matches?.(GRIP) || !core.owns(grip) || (drag && (!drag.keyboard || drag.grip !== grip))) return\n if (event.key === \"Escape\") return drag && (event.preventDefault(), cancel())\n const arrow = ARROWS[event.key]\n if (!arrow) return\n event.preventDefault()\n drag ??= begin(core, root, grip, true)\n if (!drag) return\n const [dx, dy] = arrow.map((d) => d * (event.shiftKey ? 0.1 : 0.01))\n const resize = event.altKey && kind(drag.el) === \"box\"\n const now = drag.value\n drag.value = snap(\n kind(drag.el) === \"point\"\n ? movePoint(now, dx, dy)\n : resize\n ? resizeBox(now, drag.edge ?? \"se\", dx, dy, drag.min, drag.ratio)\n : moveBox(now, dx, dy),\n )\n write(drag.el, drag.value)\n flush(drag)\n announce(drag, resize ? \"resized\" : \"moved\")\n}\n\nfunction liveRegion() {\n if (!live?.isConnected) {\n live = document.createElement(\"div\")\n live.setAttribute(\"aria-live\", \"polite\")\n live.setAttribute(\"data-reactive-drag-live\", \"\")\n live.style.cssText = \"position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap\"\n document.body.append(live)\n }\n return live\n}\n\n// Clear the region now and write the words on the next frame, so a change is\n// heard even when the words are the same as the last ones.\nfunction announce(record, name) {\n let words = json(attr(record.el, `${kind(record.el)}-messages`), {})[name] ?? \"\"\n for (const key of keysOf(record.el)) words = words.replaceAll(`{${key}}`, Math.round(record.value[key] * 100))\n const region = liveRegion()\n region.textContent = \"\"\n cancelAnimationFrame(speaking)\n speaking = requestAnimationFrame(() => (region.textContent = words))\n}\n\n// --- Aspect ---------------------------------------------------------------------------\n// The field a box reads its ratio from changed (a preset radio was picked):\n// the box re-fits to it around its centre, inside the frame — a committed\n// change, like a release. A box already at that ratio stays as it is.\nfunction aspectChanged(event, core, root) {\n const name = event.target?.name\n if (!name) return\n for (const box of root.querySelectorAll(\"[data-reactive-box-aspect]\")) {\n if (attr(box, \"box-aspect\") !== name || box === drag?.el || !core.owns(box)) continue\n const record = begin(core, root, box, true)\n const { ratio, start: b } = record ?? {}\n if (!ratio || Math.abs(b.width / b.height / ratio - 1) < 0.005) continue\n record.value = snap(fitBox(b, ratio, record.min))\n write(box, record.value)\n flush(record)\n commit(record)\n }\n}\n\n// --- Release / cancel ---------------------------------------------------------------\nfunction release() {\n const record = drag\n // A morph took the element away with no move since: what it rendered stands.\n if (!record.el.isConnected) return settle(record)\n flush(record)\n settle(record)\n commit(record)\n}\n\n// One `change` on each field the drag moved, and the action, once, when the\n// element ended somewhere new.\nfunction commit(record) {\n for (const [key, field] of Object.entries(record.fields)) {\n if (field.value !== record.before[key]) field.dispatchEvent(new Event(\"change\", { bubbles: true }))\n }\n const name = kind(record.el)\n const action = attr(record.el, `${name}-action`)\n const params = {}\n for (const key of keysOf(record.el)) params[key] = round(record.value[key])\n if (!action || keysOf(record.el).every((key) => params[key] === round(record.start[key]))) return\n record.core.proceed(record.el, action, JSON.stringify(params), json(attr(record.el, `${name}-busy`)))\n}\n\nfunction cancel() {\n const record = drag\n record.value = record.start\n write(record.el, record.start)\n flush(record)\n if (record.keyboard) announce(record, \"cancelled\")\n settle(record)\n}\n\n// Undo everything the drag set: the markers, the frame loops, the text selection.\nfunction settle(record) {\n drag = null\n record.over = true\n cancelAnimationFrame(record.raf)\n cancelAnimationFrame(record.writing)\n record.grip.removeAttribute(DRAGGING)\n record.frame.removeAttribute(ACTIVE)\n if (!record.keyboard) document.documentElement.style.userSelect = record.select ?? \"\"\n}\n\n// --- Morphs ----------------------------------------------------------------------------\n// A morph that arrives mid-drag (a reply, a broadcast) does not move the\n// dragged element, write its fields or drop the markers: the drag wins until\n// release. A morph that moved a box or point outside a drag brings its fields\n// along — they may sit outside what the morph rendered.\nfunction holdMorphs() {\n if (morphs) return\n morphs = true\n document.addEventListener(\"turbo:before-morph-attribute\", (event) => {\n const el = event.target\n const name = event.detail?.attributeName\n if (!drag) return\n const held =\n (el === drag.el && name === \"style\") ||\n (el === drag.grip && name === DRAGGING) ||\n (el === drag.frame && name === ACTIVE) ||\n (name === \"value\" && Object.values(drag.fields).includes(el))\n if (held) event.preventDefault()\n })\n // (Every morphed element fires this: the cheap check first.)\n document.addEventListener(\"turbo:morph-element\", (event) => {\n const el = event.target\n if (el === drag?.el || !el.matches?.(`${BOX},${POINT}`)) return\n const root = el.closest(ROOT)\n const core = cores.get(root)\n if (!core?.owns(el)) return\n const value = read(el)\n for (const [key, field] of Object.entries(fieldsOf(el, root, core))) {\n if (round(Number(field.value)) !== round(value[key])) core.setValue(field, String(round(value[key])))\n }\n })\n}\n"
5
+ "// phlex/reactive/features/drag — move and resize a box, or move a point,\n// inside a frame, with the result in fields as fractions of the frame (issue\n// #416). One of the feature modules the core imports on demand (issue #275);\n// in the default client it is part of the one file.\n//\n// The markup is the Ruby helpers' (Phlex::Reactive::Component::Drag):\n//\n// data-reactive-frame the coordinate space: its padding box\n// data-reactive-box='{\"x\":\"crop[x]\",…}' a box and the names of its fields\n// -min / -ratio / -aspect / -scale / -action / -busy / -messages\n// data-reactive-box-handle=\"se\" an edge or corner (inside the box)\n// data-reactive-point='{\"x\":…,\"y\":…}' a point (its centre)\n// -within / -scale / -action / -busy / -messages\n//\n// and what it sets while a pointer drag is on, for the app's CSS:\n//\n// data-reactive-frame-active on the frame\n// data-reactive-dragging on the pressed box, handle or point\n//\n// Where an element is lives in its own inline style — left/top/width/height\n// in percent, rendered by the server and written here — so a morph that\n// renders new values moves it, and nothing is cached between drags. Each\n// field's .value is the same number as a fraction, rounded to 4 decimals —\n// or, with -scale (issue #428), that fraction times the scale (100: percent,\n// 2 decimals) — written as set_value writes (core.setValue: a bubbling\n// `input` when it moved), at most once a frame while a pointer drags; one\n// `change` follows on release. A box's x/y/width/height are fractions of the\n// frame; a point's x/y of the element it sits in that matches its -within\n// selector, else of the frame. Everything else — the style, the action's\n// params, the words — stays in fractions.\n//\n// Input is pointer events: mouse, pen and touch behave the same (the helpers\n// render touch-action: none, so a finger drag does not scroll), and a drag\n// starts after a 4 px move. The keyboard moves the focused box or point\n// directly — arrows 1%, Shift 10%, Alt resizes — and the key's release is\n// the release. A declared action is sent once per release through the\n// root's own pipeline (core.proceed).\n//\n// This module never imports the core: it reaches a controller through the\n// `core` handle (core.owns, core.proceed, core.setValue).\n\nconst FRAME = \"[data-reactive-frame]\"\nconst BOX = \"[data-reactive-box]\"\nconst POINT = \"[data-reactive-point]\"\nconst HANDLE = \"[data-reactive-box-handle]\"\nconst GRIP = `${HANDLE},${POINT},${BOX}`\nconst ROOT = '[data-controller~=\"reactive\"]'\nconst FIELD = \"input,textarea,select,[contenteditable]\"\nconst DRAGGING = \"data-reactive-dragging\"\nconst ACTIVE = \"data-reactive-frame-active\"\nconst THRESHOLD = 4 // px a press must move before it is a drag\nconst EDGE = 40 // px from the window's top or bottom where auto-scroll starts\nconst SPEED = 12 // px auto-scroll moves per frame\nconst ARROWS = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }\nconst STYLE = { x: \"left\", y: \"top\", width: \"width\", height: \"height\" }\n\n// controller -> the removals of what connect() added.\nconst wired = new WeakMap()\n// reactive root element -> its core handle, for a morph of a box in it.\nconst cores = new WeakMap()\n// The one drag on the page, by pointer or keyboard, or null.\nlet drag = null\n// The page's live region (re-created after a Turbo visit replaced <body>),\n// and the frame that will write its next words.\nlet live\nlet speaking\nlet morphs = false\n\nexport function connect(controller, core) {\n const root = controller.element\n cores.set(root, core)\n // Empty on the page before anything is announced (a screen reader often\n // ignores a region that arrives already holding words).\n liveRegion()\n holdMorphs()\n const on = [\n [\"pointerdown\", (event) => pointerDown(event, core, root)],\n [\"keydown\", (event) => keyDown(event, core, root)],\n [\"keyup\", (event) => drag?.keyboard && event.target === drag.grip && ARROWS[event.key] && release()],\n [\"focusout\", (event) => drag?.keyboard && event.target === drag.grip && release()],\n [\"input\", (event) => aspectChanged(event, core, root)],\n [\"change\", (event) => aspectChanged(event, core, root)],\n ]\n for (const [type, fn] of on) root.addEventListener(type, fn)\n wired.set(controller, () => {\n for (const [type, fn] of on) root.removeEventListener(type, fn)\n })\n}\n\nexport function disconnect(controller) {\n wired.get(controller)?.()\n wired.delete(controller)\n cores.delete(controller.element)\n // A root that leaves mid-drag (a replace, a navigation) ends its drag.\n if (drag?.root === controller.element) settle(drag)\n}\n\n// --- Geometry: fractions, 0..1 on each axis --------------------------------------\nconst clamp = (value, low, high) => Math.min(Math.max(value, low), high)\n\nexport const round = (value) => Math.round(value * 1e4) / 1e4\n\n// What a field on `scale` holds for a fraction: its 4 decimals times the\n// scale, to 15 significant digits — every digit it has, without the float\n// noise: 0.1235 is \"0.1235\", \"12.35\" on 100 (not 12.350000000000001), \"3.0875\"\n// on 25. (A product that rounds up past the largest number stays as it is.)\nexport function scaled(value, scale = 1) {\n const exact = round(value) * scale\n const clean = Number(exact.toPrecision(15))\n return String(Number.isFinite(clean) ? clean : exact)\n}\n\n// A position as the fields will hold it: each number rounded to 4 decimals,\n// a box's x/y then pulled in so it still ends inside the frame (rounded\n// apart, 0.45545 + 0.54455 is 0.4555 + 0.5446 = 1.0001).\nexport function snap(value) {\n const out = { x: round(value.x), y: round(value.y) }\n if (value.width != null) {\n out.width = round(value.width)\n out.height = round(value.height)\n out.x = Math.min(out.x, round(1 - out.width))\n out.y = Math.min(out.y, round(1 - out.height))\n }\n return out\n}\n\nexport function moveBox(b, dx, dy) {\n return { x: clamp(b.x + dx, 0, 1 - b.width), y: clamp(b.y + dy, 0, 1 - b.height), width: b.width, height: b.height }\n}\n\nexport function movePoint(p, dx, dy) {\n return { x: clamp(p.x + dx, 0, 1), y: clamp(p.y + dy, 0, 1) }\n}\n\n// Resize from `edge` (a handle: n, se, …) by (dx, dy), the opposite side\n// staying put, inside the frame and never below `min`. `ratio` is the locked\n// width over height IN FRACTIONS (0: free): on a corner the axis the pointer\n// moved more drives, on an edge the other axis grows around the box's centre.\nexport function resizeBox(b, edge, dx, dy, min, ratio) {\n const [n, e, s, w] = [\"n\", \"e\", \"s\", \"w\"].map((side) => edge.includes(side))\n const right = b.x + b.width\n const bottom = b.y + b.height\n const cx = b.x + b.width / 2\n const cy = b.y + b.height / 2\n // From the side that stays to the frame's edge (an axis no handle drives,\n // with a ratio: around its centre).\n const roomW = e ? 1 - b.x : w ? right : ratio ? 2 * Math.min(cx, 1 - cx) : 1\n const roomH = s ? 1 - b.y : n ? bottom : ratio ? 2 * Math.min(cy, 1 - cy) : 1\n let width = b.width + (e ? dx : w ? -dx : 0)\n let height = b.height + (s ? dy : n ? -dy : 0)\n if (ratio) {\n const byWidth = (e || w) && (!(n || s) || Math.abs(width - b.width) >= Math.abs(height - b.height) * ratio)\n const high = Math.min(roomW, roomH * ratio)\n width = clamp(byWidth ? width : height * ratio, Math.min(Math.max(min, min * ratio), high), high)\n height = width / ratio\n } else {\n if (e || w) width = clamp(width, Math.min(min, roomW), roomW)\n if (n || s) height = clamp(height, Math.min(min, roomH), roomH)\n }\n return { x: w ? right - width : e ? b.x : cx - width / 2, y: n ? bottom - height : s ? b.y : cy - height / 2, width, height }\n}\n\n// The box at a new `ratio` (fractions): the same area, centred where it was,\n// inside the frame and no smaller than `min`.\nexport function fitBox(b, ratio, min) {\n const high = Math.min(1, ratio)\n const width = clamp(Math.sqrt(b.width * b.height * ratio), Math.min(Math.max(min, min * ratio), high), high)\n const height = width / ratio\n return {\n x: clamp(b.x + b.width / 2 - width / 2, 0, 1 - width),\n y: clamp(b.y + b.height / 2 - height / 2, 0, 1 - height),\n width,\n height,\n }\n}\n\n// \"16:9\", \"1.5\" or \"\" (free): width over height, or 0 for free.\nexport function parseRatio(text) {\n const [width, height = 1, extra] = String(text ?? \"\").split(\":\").map(Number)\n const ratio = extra === undefined ? width / height : 0\n return ratio > 0 && Number.isFinite(ratio) ? ratio : 0\n}\n\n// --- Reading and writing an element ------------------------------------------------\nconst attr = (el, name) => el?.getAttribute?.(`data-reactive-${name}`)\nconst json = (value, fallback) => {\n try {\n return value == null ? fallback : JSON.parse(value)\n } catch {\n return fallback\n }\n}\nconst kind = (el) => (el.matches(POINT) ? \"point\" : \"box\")\nconst scaleOf = (el) => Number(attr(el, `${kind(el)}-scale`)) || 1\nconst keysOf = (el) => (kind(el) === \"point\" ? [\"x\", \"y\"] : [\"x\", \"y\", \"width\", \"height\"])\n\nfunction read(el) {\n const value = {}\n for (const key of keysOf(el)) value[key] = (parseFloat(el.style[STYLE[key]]) || 0) / 100\n return value\n}\n\nfunction write(el, value) {\n for (const key of keysOf(el)) el.style[STYLE[key]] = `${Math.round(value[key] * 1e4) / 100}%`\n}\n\n// An element's padding box in viewport px: what CSS % on a child resolves against.\nfunction paddingBox(el) {\n const rect = el.getBoundingClientRect()\n return {\n left: rect.left + (el.clientLeft || 0),\n top: rect.top + (el.clientTop || 0),\n width: el.clientWidth || rect.width,\n height: el.clientHeight || rect.height,\n }\n}\n\n// A point's bounds: the element it sits in that matches -within, else its frame.\nfunction boundsOf(el, frame) {\n try {\n return el.parentElement.closest(attr(el, \"point-within\") || FRAME) ?? frame\n } catch {\n return frame // an invalid selector\n }\n}\n\n// The root's own fields of these names (a nested root's are its own, issue #15).\nfunction named(root, core, name) {\n return [...root.querySelectorAll(\"[name]\")].filter((field) => field.name === name && core.owns(field))\n}\n\nfunction fieldsOf(el, root, core) {\n const names = json(attr(el, kind(el)), {})\n const fields = {}\n for (const key of keysOf(el)) {\n const [field] = named(root, core, names[key])\n if (field) fields[key] = field\n }\n return fields\n}\n\n// The locked ratio in fractions (the pixel ratio over the frame's), or 0.\nfunction ratioOf(box, frame, root, core) {\n const name = attr(box, \"box-aspect\")\n const field = name && named(root, core, name).find((f) => !/^(radio|checkbox)$/.test(f.type) || f.checked)\n const ratio = name ? parseRatio(field?.value) : Number(attr(box, \"box-ratio\")) || 0\n const { width, height } = paddingBox(frame)\n return ratio && width && height ? (ratio * height) / width : 0\n}\n\n// What a drag (or a key, or a re-fit) works on, read off the element now.\nfunction begin(core, root, grip, keyboard) {\n const el = grip.matches(HANDLE) ? grip.closest(BOX) : grip\n const frame = el?.closest(FRAME)\n if (!frame) return null\n const value = read(el)\n const fields = fieldsOf(el, root, core)\n const point = kind(el) === \"point\"\n return {\n core,\n root,\n grip,\n el,\n frame,\n keyboard,\n bounds: point ? boundsOf(el, frame) : frame,\n edge: attr(grip, \"box-handle\"),\n start: value,\n value,\n ratio: point ? 0 : ratioOf(el, frame, root, core),\n min: Number(attr(el, \"box-min\")) || 0,\n scale: scaleOf(el),\n fields,\n before: Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, field.value])),\n }\n}\n\n// Write a field the fraction on its scale, as set_value writes — unless it\n// already holds that number, within half a step of the fraction's 4th\n// decimal on its scale (\"50.0\", the 50 a server rendered for 0.5 × 100, is\n// not a move: no `input`, and no `change` on release).\nfunction put(core, field, value, scale) {\n const text = scaled(value, scale)\n if (field.value === \"\" || !(Math.abs(field.value - text) < scale / 2e4)) core.setValue(field, text)\n}\n\n// Write the fields now (set_value's write: `input` on each that moved).\nfunction flush(record) {\n cancelAnimationFrame(record.writing)\n record.writing = 0\n for (const [key, field] of Object.entries(record.fields)) put(record.core, field, record.value[key], record.scale)\n}\n\n// --- Pointer --------------------------------------------------------------------------\nfunction pointerDown(event, core, root) {\n forgetRemoved()\n if (drag || event.button > 0 || event.isPrimary === false || event.target.closest?.(FIELD)) return\n const grip = event.target.closest?.(GRIP)\n const record = grip && core.owns(grip) && begin(core, root, grip, false)\n if (!record) return\n const x0 = event.clientX\n const y0 = event.clientY\n const bounds = paddingBox(record.bounds)\n record.p0 = [(x0 - bounds.left) / bounds.width, (y0 - bounds.top) / bounds.height]\n // Only the pointer that pressed drives the drag: a second finger is not it.\n const mine = (e) => e.pointerId === event.pointerId\n const move = (e) => {\n // (A drag that ended before the release — its element left — stays over.)\n if (!mine(e) || record.over) return\n if (drag) return follow(e.clientX, e.clientY)\n if (Math.abs(e.clientX - x0) <= THRESHOLD && Math.abs(e.clientY - y0) <= THRESHOLD) return\n start(record)\n follow(e.clientX, e.clientY)\n }\n const up = (e) => {\n if (!mine(e)) return\n end()\n if (drag) suppressClick(), release()\n }\n const abort = (e) => {\n if (e.type === \"pointercancel\" && !mine(e)) return\n end()\n if (drag) cancel()\n }\n const escape = (e) => e.key === \"Escape\" && drag && (e.preventDefault(), abort(e))\n const on = [\n [\"pointermove\", move],\n [\"pointerup\", up],\n [\"pointercancel\", abort],\n [\"keydown\", escape],\n ]\n for (const [type, fn] of on) document.addEventListener(type, fn)\n function end() {\n for (const [type, fn] of on) document.removeEventListener(type, fn)\n }\n}\n\n// The click a pointer drag ends with is not one.\nfunction suppressClick() {\n const stop = (event) => (event.preventDefault(), event.stopPropagation())\n window.addEventListener(\"click\", stop, { capture: true, once: true })\n setTimeout(() => window.removeEventListener(\"click\", stop, { capture: true }))\n}\n\nfunction start(record) {\n drag = record\n record.grip.setAttribute(DRAGGING, \"\")\n record.frame.setAttribute(ACTIVE, \"\")\n record.select = document.documentElement.style.userSelect\n document.documentElement.style.userSelect = \"none\"\n}\n\n// The pointer is at (x, y): the element goes where the pointer's move since\n// the press puts it, measured in its bounds as they are NOW (a scroll moved them).\nfunction follow(x, y) {\n const record = drag\n // A morph or a stream took the element away: the drag is over.\n if (!record.el.isConnected) return settle(record)\n record.x = x\n record.y = y\n const bounds = paddingBox(record.bounds)\n const dx = (x - bounds.left) / bounds.width - record.p0[0]\n const dy = (y - bounds.top) / bounds.height - record.p0[1]\n const { start: from, edge } = record\n record.value = snap(\n kind(record.el) === \"point\"\n ? movePoint(from, dx, dy)\n : edge\n ? resizeBox(from, edge, dx, dy, record.min, record.ratio)\n : moveBox(from, dx, dy),\n )\n write(record.el, record.value)\n record.writing ||= requestAnimationFrame(() => flush(record))\n scroll(y)\n}\n\n// Near the window's top or bottom edge, scroll one step a frame until the\n// pointer leaves the edge (a frame taller than the window).\nfunction scroll(y) {\n drag.wy = y < EDGE ? -SPEED : y > window.innerHeight - EDGE ? SPEED : 0\n if (drag.wy && !drag.raf) {\n const step = () => {\n if (!drag?.wy) return drag && (drag.raf = 0)\n window.scrollBy(0, drag.wy)\n follow(drag.x, drag.y)\n if (drag) drag.raf = requestAnimationFrame(step)\n }\n drag.raf = requestAnimationFrame(step)\n }\n}\n\n// A drag whose element left the page (a stream replaced it while an arrow\n// was held: no keyup or focusout reaches it) is over, not in the way.\nfunction forgetRemoved() {\n if (drag && !drag.el.isConnected) settle(drag)\n}\n\n// --- Keyboard --------------------------------------------------------------------------\n// A focused box, handle or point: the arrows move it (Shift: ten times as\n// far), Alt+arrows resize a box from the focused handle or the bottom-right\n// corner, Escape puts it back. The key's release ends the step (`change`, the\n// action); each step is announced in the element's own words.\nfunction keyDown(event, core, root) {\n forgetRemoved()\n const grip = event.target\n // (A pointer drag, or a key held on another element, owns the drag.)\n if (!grip?.matches?.(GRIP) || !core.owns(grip) || (drag && (!drag.keyboard || drag.grip !== grip))) return\n if (event.key === \"Escape\") return drag && (event.preventDefault(), cancel())\n const arrow = ARROWS[event.key]\n if (!arrow) return\n event.preventDefault()\n drag ??= begin(core, root, grip, true)\n if (!drag) return\n const [dx, dy] = arrow.map((d) => d * (event.shiftKey ? 0.1 : 0.01))\n const resize = event.altKey && kind(drag.el) === \"box\"\n const now = drag.value\n drag.value = snap(\n kind(drag.el) === \"point\"\n ? movePoint(now, dx, dy)\n : resize\n ? resizeBox(now, drag.edge ?? \"se\", dx, dy, drag.min, drag.ratio)\n : moveBox(now, dx, dy),\n )\n write(drag.el, drag.value)\n flush(drag)\n announce(drag, resize ? \"resized\" : \"moved\")\n}\n\nfunction liveRegion() {\n if (!live?.isConnected) {\n live = document.createElement(\"div\")\n live.setAttribute(\"aria-live\", \"polite\")\n live.setAttribute(\"data-reactive-drag-live\", \"\")\n live.style.cssText = \"position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap\"\n document.body.append(live)\n }\n return live\n}\n\n// Clear the region now and write the words on the next frame, so a change is\n// heard even when the words are the same as the last ones.\nfunction announce(record, name) {\n let words = json(attr(record.el, `${kind(record.el)}-messages`), {})[name] ?? \"\"\n for (const key of keysOf(record.el)) words = words.replaceAll(`{${key}}`, Math.round(record.value[key] * 100))\n const region = liveRegion()\n region.textContent = \"\"\n cancelAnimationFrame(speaking)\n speaking = requestAnimationFrame(() => (region.textContent = words))\n}\n\n// --- Aspect ---------------------------------------------------------------------------\n// The field a box reads its ratio from changed (a preset radio was picked):\n// the box re-fits to it around its centre, inside the frame — a committed\n// change, like a release. A box already at that ratio stays as it is.\nfunction aspectChanged(event, core, root) {\n const name = event.target?.name\n if (!name) return\n for (const box of root.querySelectorAll(\"[data-reactive-box-aspect]\")) {\n if (attr(box, \"box-aspect\") !== name || box === drag?.el || !core.owns(box)) continue\n const record = begin(core, root, box, true)\n const { ratio, start: b } = record ?? {}\n if (!ratio || Math.abs(b.width / b.height / ratio - 1) < 0.005) continue\n record.value = snap(fitBox(b, ratio, record.min))\n write(box, record.value)\n flush(record)\n commit(record)\n }\n}\n\n// --- Release / cancel ---------------------------------------------------------------\nfunction release() {\n const record = drag\n // A morph took the element away with no move since: what it rendered stands.\n if (!record.el.isConnected) return settle(record)\n flush(record)\n settle(record)\n commit(record)\n}\n\n// One `change` on each field the drag moved, and the action, once, when the\n// element ended somewhere new.\nfunction commit(record) {\n for (const [key, field] of Object.entries(record.fields)) {\n if (field.value !== record.before[key]) field.dispatchEvent(new Event(\"change\", { bubbles: true }))\n }\n const name = kind(record.el)\n const action = attr(record.el, `${name}-action`)\n const params = {}\n for (const key of keysOf(record.el)) params[key] = round(record.value[key])\n if (!action || keysOf(record.el).every((key) => params[key] === round(record.start[key]))) return\n record.core.proceed(record.el, action, JSON.stringify(params), json(attr(record.el, `${name}-busy`)))\n}\n\nfunction cancel() {\n const record = drag\n record.value = record.start\n write(record.el, record.start)\n flush(record)\n if (record.keyboard) announce(record, \"cancelled\")\n settle(record)\n}\n\n// Undo everything the drag set: the markers, the frame loops, the text selection.\nfunction settle(record) {\n drag = null\n record.over = true\n cancelAnimationFrame(record.raf)\n cancelAnimationFrame(record.writing)\n record.grip.removeAttribute(DRAGGING)\n record.frame.removeAttribute(ACTIVE)\n if (!record.keyboard) document.documentElement.style.userSelect = record.select ?? \"\"\n}\n\n// --- Morphs ----------------------------------------------------------------------------\n// A morph that arrives mid-drag (a reply, a broadcast) does not move the\n// dragged element, write its fields or drop the markers: the drag wins until\n// release. A morph that moved a box or point outside a drag brings its fields\n// along — they may sit outside what the morph rendered.\nfunction holdMorphs() {\n if (morphs) return\n morphs = true\n document.addEventListener(\"turbo:before-morph-attribute\", (event) => {\n const el = event.target\n const name = event.detail?.attributeName\n if (!drag) return\n const held =\n (el === drag.el && name === \"style\") ||\n (el === drag.grip && name === DRAGGING) ||\n (el === drag.frame && name === ACTIVE) ||\n (name === \"value\" && Object.values(drag.fields).includes(el))\n if (held) event.preventDefault()\n })\n // (Every morphed element fires this: the cheap check first.)\n document.addEventListener(\"turbo:morph-element\", (event) => {\n const el = event.target\n if (el === drag?.el || !el.matches?.(`${BOX},${POINT}`)) return\n const root = el.closest(ROOT)\n const core = cores.get(root)\n if (!core?.owns(el)) return\n const value = read(el)\n const scale = scaleOf(el)\n for (const [key, field] of Object.entries(fieldsOf(el, root, core))) put(core, field, value[key], scale)\n })\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "94F6177CA2EF4E8764756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "0D638B03EC2311D064756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -177,7 +177,7 @@ function ownedClientOps(root, allRoots) {
177
177
  const records = (raw ?? "").split(" ").map(parseJson).filter((r) => r && typeof r === "object" && !Array.isArray(r))
178
178
  if (records.length === 0) return [{ event, ops: raw }]
179
179
  // A shortcut record (issue #357) reads as "keydown@window shortcut=mod+z".
180
- const shortcutOf = (s) => (typeof s === "object" ? `${s.keys}${s.inEditable ? " editable" : ""}` : s)
180
+ const shortcutOf = (s) => (typeof s === "object" ? `${s.keys}${s.inEditable ? " editable" : ""}${s.handled ? " handled" : ""}` : s)
181
181
  const summary = (r) => `${r.on}${r.window ? "@window" : ""}${r.shortcut == null ? "" : ` shortcut=${shortcutOf(r.shortcut)}`}`
182
182
  return records.map((r) => ({ event: r.on ? summary(r) : event, ops: r.ops }))
183
183
  })
@@ -228,7 +228,8 @@ function ownedText(root, allRoots) {
228
228
  }
229
229
 
230
230
  // reactive_filter lives on the ROOT itself (the input/option/group/empty
231
- // selectors it narrows client-side). Null when the root declares no filter.
231
+ // selectors it narrows client-side, and how it matches: issue #425's mode and
232
+ // fold). Null when the root declares no filter.
232
233
  function readFilter(el) {
233
234
  const input = el.getAttribute("data-reactive-filter-input")
234
235
  if (!input) return null
@@ -237,5 +238,7 @@ function readFilter(el) {
237
238
  option: el.getAttribute("data-reactive-filter-option"),
238
239
  group: el.getAttribute("data-reactive-filter-group"),
239
240
  empty: el.getAttribute("data-reactive-filter-empty"),
241
+ match: el.getAttribute("data-reactive-filter-match") ?? "substring",
242
+ fold: el.hasAttribute("data-reactive-filter-fold"),
240
243
  }
241
244
  }
@@ -1,4 +1,4 @@
1
- function m(t=document){let r=Array.from(t.querySelectorAll('[data-controller~="reactive"]'));return r.map((e)=>b(e,r))}function T(t=document){let r=m(t),e=globalThis.console;if(e&&typeof e.log==="function")e.log(`[phlex-reactive] ${r.length} reactive root(s)`);if(e&&typeof e.table==="function")e.table(r.map((a)=>({id:a.id,component:a.component??(a.opaque?"(opaque token)":null),actions:a.triggers.map((o)=>o.action).join(", "),fields:a.fields.join(", "),computes:a.computes.join(", ")})));return r}function b(t,r){let e=t.getAttribute("data-reactive-token-value"),a=g(e);return{id:t.id||null,component:a.opaque?null:a.c??null,gid:a.gid??null,stateKeys:a.s?Object.keys(a.s):[],tokenVersion:a.v??null,opaque:!!a.opaque,status:v(t),triggers:h(t,r),clientOps:w(t,r),computes:q(t,r),fields:O(t,r),show:x(t,r),text:j(t,r),filter:S(t)}}function g(t){if(!t)return{opaque:!0};try{let r=t.split("--")[0],e=l(r),a=JSON.parse(e),o=a&&a._rails&&a._rails.data?a._rails.data:a;if(!o||typeof o!=="object")return{opaque:!0};return o}catch{return{opaque:!0}}}function l(t){return(globalThis.atob||((e)=>Buffer.from(e,"base64").toString("binary")))(t)}function v(t){return{error:t.getAttribute("data-reactive-error"),busy:t.getAttribute("aria-busy")==="true"||t.hasAttribute("data-reactive-busy"),dirty:t.hasAttribute("data-reactive-dirty")}}var A='[data-controller~="reactive"]';function y(t,r){return t.closest(A)===r}function i(t,r,e){return Array.from(t.querySelectorAll(r)).filter((a)=>y(a,t))}function h(t,r){return i(t,"[data-reactive-action-param]",r).map((e)=>({action:e.getAttribute("data-reactive-action-param"),event:d(e.getAttribute("data-action")),params:e.getAttribute("data-reactive-params-param"),debounce:e.getAttribute("data-reactive-debounce-param"),throttle:e.getAttribute("data-reactive-throttle-param"),confirm:e.getAttribute("data-reactive-confirm-param"),optimistic:e.getAttribute("data-reactive-optimistic-param"),loading:e.getAttribute("data-reactive-loading-param"),shortcut:e.getAttribute("data-reactive-shortcut-param")}))}function d(t){if(!t)return null;let r=t.indexOf("->");return r===-1?null:t.slice(0,r)}function w(t,r){return i(t,"[data-reactive-ops-param]",r).flatMap((e)=>{let a=e.getAttribute("data-reactive-ops-param"),o=d(e.getAttribute("data-action")),u=s(a);if(Array.isArray(u))return[{event:o,ops:u}];let c=(a??"").split(" ").map(s).filter((n)=>n&&typeof n==="object"&&!Array.isArray(n));if(c.length===0)return[{event:o,ops:a}];let p=(n)=>typeof n==="object"?`${n.keys}${n.inEditable?" editable":""}`:n,f=(n)=>`${n.on}${n.window?"@window":""}${n.shortcut==null?"":` shortcut=${p(n.shortcut)}`}`;return c.map((n)=>({event:n.on?f(n):o,ops:n.ops}))})}function s(t){try{return JSON.parse(t)}catch{return}}function q(t,r){return i(t,"[data-reactive-compute-reducer-param]",r).map((e)=>e.getAttribute("data-reactive-compute-reducer-param"))}function O(t,r){return i(t,"input[name], select[name], textarea[name]",r).map((e)=>e.getAttribute("name"))}function x(t,r){return i(t,"[data-reactive-show-field]",r).map((e)=>({field:e.getAttribute("data-reactive-show-field"),equals:e.getAttribute("data-reactive-show-equals"),not:e.getAttribute("data-reactive-show-not"),in:e.getAttribute("data-reactive-show-in")}))}function j(t,r){return i(t,"[data-reactive-text]",r).map((e)=>e.getAttribute("data-reactive-text"))}function S(t){let r=t.getAttribute("data-reactive-filter-input");if(!r)return null;return{input:r,option:t.getAttribute("data-reactive-filter-option"),group:t.getAttribute("data-reactive-filter-group"),empty:t.getAttribute("data-reactive-filter-empty")}}export{T as report,m as scan};
1
+ function m(t=document){let r=Array.from(t.querySelectorAll('[data-controller~="reactive"]'));return r.map((e)=>b(e,r))}function T(t=document){let r=m(t),e=globalThis.console;if(e&&typeof e.log==="function")e.log(`[phlex-reactive] ${r.length} reactive root(s)`);if(e&&typeof e.table==="function")e.table(r.map((a)=>({id:a.id,component:a.component??(a.opaque?"(opaque token)":null),actions:a.triggers.map((i)=>i.action).join(", "),fields:a.fields.join(", "),computes:a.computes.join(", ")})));return r}function b(t,r){let e=t.getAttribute("data-reactive-token-value"),a=l(e);return{id:t.id||null,component:a.opaque?null:a.c??null,gid:a.gid??null,stateKeys:a.s?Object.keys(a.s):[],tokenVersion:a.v??null,opaque:!!a.opaque,status:v(t),triggers:h(t,r),clientOps:w(t,r),computes:q(t,r),fields:O(t,r),show:x(t,r),text:j(t,r),filter:S(t)}}function l(t){if(!t)return{opaque:!0};try{let r=t.split("--")[0],e=g(r),a=JSON.parse(e),i=a&&a._rails&&a._rails.data?a._rails.data:a;if(!i||typeof i!=="object")return{opaque:!0};return i}catch{return{opaque:!0}}}function g(t){return(globalThis.atob||((e)=>Buffer.from(e,"base64").toString("binary")))(t)}function v(t){return{error:t.getAttribute("data-reactive-error"),busy:t.getAttribute("aria-busy")==="true"||t.hasAttribute("data-reactive-busy"),dirty:t.hasAttribute("data-reactive-dirty")}}var A='[data-controller~="reactive"]';function y(t,r){return t.closest(A)===r}function o(t,r,e){return Array.from(t.querySelectorAll(r)).filter((a)=>y(a,t))}function h(t,r){return o(t,"[data-reactive-action-param]",r).map((e)=>({action:e.getAttribute("data-reactive-action-param"),event:d(e.getAttribute("data-action")),params:e.getAttribute("data-reactive-params-param"),debounce:e.getAttribute("data-reactive-debounce-param"),throttle:e.getAttribute("data-reactive-throttle-param"),confirm:e.getAttribute("data-reactive-confirm-param"),optimistic:e.getAttribute("data-reactive-optimistic-param"),loading:e.getAttribute("data-reactive-loading-param"),shortcut:e.getAttribute("data-reactive-shortcut-param")}))}function d(t){if(!t)return null;let r=t.indexOf("->");return r===-1?null:t.slice(0,r)}function w(t,r){return o(t,"[data-reactive-ops-param]",r).flatMap((e)=>{let a=e.getAttribute("data-reactive-ops-param"),i=d(e.getAttribute("data-action")),u=s(a);if(Array.isArray(u))return[{event:i,ops:u}];let c=(a??"").split(" ").map(s).filter((n)=>n&&typeof n==="object"&&!Array.isArray(n));if(c.length===0)return[{event:i,ops:a}];let p=(n)=>typeof n==="object"?`${n.keys}${n.inEditable?" editable":""}${n.handled?" handled":""}`:n,f=(n)=>`${n.on}${n.window?"@window":""}${n.shortcut==null?"":` shortcut=${p(n.shortcut)}`}`;return c.map((n)=>({event:n.on?f(n):i,ops:n.ops}))})}function s(t){try{return JSON.parse(t)}catch{return}}function q(t,r){return o(t,"[data-reactive-compute-reducer-param]",r).map((e)=>e.getAttribute("data-reactive-compute-reducer-param"))}function O(t,r){return o(t,"input[name], select[name], textarea[name]",r).map((e)=>e.getAttribute("name"))}function x(t,r){return o(t,"[data-reactive-show-field]",r).map((e)=>({field:e.getAttribute("data-reactive-show-field"),equals:e.getAttribute("data-reactive-show-equals"),not:e.getAttribute("data-reactive-show-not"),in:e.getAttribute("data-reactive-show-in")}))}function j(t,r){return o(t,"[data-reactive-text]",r).map((e)=>e.getAttribute("data-reactive-text"))}function S(t){let r=t.getAttribute("data-reactive-filter-input");if(!r)return null;return{input:r,option:t.getAttribute("data-reactive-filter-option"),group:t.getAttribute("data-reactive-filter-group"),empty:t.getAttribute("data-reactive-filter-empty"),match:t.getAttribute("data-reactive-filter-match")??"substring",fold:t.hasAttribute("data-reactive-filter-fold")}}export{T as report,m as scan};
2
2
 
3
- //# debugId=B4494A595CB8588264756E2164756E21
3
+ //# debugId=0503DAE97DCB3C7564756E2164756E21
4
4
  //# sourceMappingURL=inspect.min.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["inspect.js"],
4
4
  "sourcesContent": [
5
- "// The on-demand client inspector (issue #168) — the browser half of the\n// debugging toolkit, the client twin of `phlex_reactive:actions` / the MCP tools.\n//\n// It is a STANDALONE module (the confirm.js / compute.js precedent): it does NOT\n// touch the hot-path reactive_controller.js and costs NOTHING until you import\n// it. Loaded on demand from the console (the engine pins it), it scans the live\n// DOM and maps every reactive root + bound trigger back to the server\n// Component#action names — so you can answer \"what's reactive on this page, what\n// does each control POST, and which component owns it?\" without reading source.\n//\n// (await import(\"phlex/reactive/inspect\")).report() // console.table\n// const roots = (await import(\"phlex/reactive/inspect\")).scan() // data\n//\n// The server↔client mapping is BY NAME: scan()'s `component` + each trigger's\n// `action` are exactly the Component#action identifiers `phlex_reactive:actions`\n// and the MCP tools list. The docs page shows the two side by side.\n//\n// It reads the SAME DOM contract reactive_controller.js writes (roots\n// [data-controller~=\"reactive\"] with id + data-reactive-token-value; triggers\n// [data-reactive-action-param] with data-action/params/modifiers; client-only\n// [data-reactive-ops-param]; computes [data-reactive-compute-reducer-param]; the\n// show/filter/text binding families). It never mutates anything — pure read.\n\n// Scan `root` (default the whole document) and return one entry per reactive\n// root, deepest-scoped: each trigger is attributed to its NEAREST reactive root,\n// so a nested root doesn't double-count its parent's triggers.\nexport function scan(root = document) {\n const roots = Array.from(root.querySelectorAll('[data-controller~=\"reactive\"]'))\n return roots.map((el) => inspectRoot(el, roots))\n}\n\n// console.table the inventory (roots + a flattened trigger table), and return\n// the same data scan() does so `report()` is also usable programmatically. Safe\n// on an empty document (logs an empty table, throws nothing).\nexport function report(root = document) {\n const roots = scan(root)\n const console = globalThis.console\n\n // Guard each console method independently — a host page (or a test harness)\n // may ship a partial console, and report() must never throw over logging.\n if (console && typeof console.log === \"function\") {\n console.log(`[phlex-reactive] ${roots.length} reactive root(s)`)\n }\n if (console && typeof console.table === \"function\") {\n console.table(\n roots.map((r) => ({\n id: r.id,\n component: r.component ?? (r.opaque ? \"(opaque token)\" : null),\n actions: r.triggers.map((t) => t.action).join(\", \"),\n fields: r.fields.join(\", \"),\n computes: r.computes.join(\", \"),\n })),\n )\n }\n return roots\n}\n\n// --- one root --------------------------------------------------------------\n\nfunction inspectRoot(el, allRoots) {\n const token = el.getAttribute(\"data-reactive-token-value\")\n const payload = decodeToken(token)\n\n return {\n id: el.id || null,\n component: payload.opaque ? null : (payload.c ?? null),\n gid: payload.gid ?? null,\n stateKeys: payload.s ? Object.keys(payload.s) : [],\n tokenVersion: payload.v ?? null,\n opaque: !!payload.opaque,\n status: readStatus(el),\n triggers: ownedTriggers(el, allRoots),\n clientOps: ownedClientOps(el, allRoots),\n computes: ownedComputes(el, allRoots),\n fields: ownedFields(el, allRoots),\n show: ownedShow(el, allRoots),\n text: ownedText(el, allRoots),\n filter: readFilter(el),\n }\n}\n\n// The signed identity is base64(JSON)--signature under the Rails 7.1+ JSON\n// message serializer. Decode the segment BEFORE the \"--\", JSON-parse it, and\n// unwrap the MessageVerifier envelope ({_rails:{data:…}}) to the {c,gid,s,v}\n// payload. Degrade to { opaque: true } on anything that isn't JSON-decodable —\n// a Marshal-serialized payload (older apps) simply isn't readable client-side,\n// and that's fine: the inspector shows \"(opaque token)\" rather than throwing.\nfunction decodeToken(token) {\n if (!token) return { opaque: true }\n\n try {\n const segment = token.split(\"--\")[0]\n const json = atobUtf8(segment)\n const parsed = JSON.parse(json)\n const data = parsed && parsed._rails && parsed._rails.data ? parsed._rails.data : parsed\n if (!data || typeof data !== \"object\") return { opaque: true }\n return data\n } catch {\n return { opaque: true }\n }\n}\n\n// base64 → string. Uses the platform atob; a malformed/urlsafe segment throws,\n// which decodeToken catches and reports as opaque.\nfunction atobUtf8(b64) {\n const decode = globalThis.atob || ((s) => Buffer.from(s, \"base64\").toString(\"binary\"))\n return decode(b64)\n}\n\n// The at-a-glance runtime state the controller stamps on the root. Names/flags\n// only — never a value that could be sensitive.\nfunction readStatus(el) {\n return {\n error: el.getAttribute(\"data-reactive-error\"),\n busy: el.getAttribute(\"aria-busy\") === \"true\" || el.hasAttribute(\"data-reactive-busy\"),\n dirty: el.hasAttribute(\"data-reactive-dirty\"),\n }\n}\n\n// --- ownership scoping -----------------------------------------------------\n//\n// An element belongs to a root when its NEAREST reactive-root ancestor is that\n// root (matching the controller's own ownership walk). closest() finds the\n// nearest ancestor matching the selector; the element itself can be the root\n// (for a root that also carries a binding), so we start the walk from the\n// element and accept `el` as its own owner.\n\nconst ROOT_SELECTOR = '[data-controller~=\"reactive\"]'\n\nfunction ownedBy(node, root) {\n return node.closest(ROOT_SELECTOR) === root\n}\n\n// Query `root` for `selector`, keeping only elements whose nearest reactive\n// root IS `root` — so a nested root's matches are excluded here and attributed\n// to that inner root instead.\nfunction scopedQuery(root, selector, allRoots) {\n return Array.from(root.querySelectorAll(selector)).filter((node) => ownedBy(node, root))\n}\n\n// --- the binding families --------------------------------------------------\n\nfunction ownedTriggers(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-action-param]\", allRoots).map((el) => ({\n action: el.getAttribute(\"data-reactive-action-param\"),\n event: eventOf(el.getAttribute(\"data-action\")),\n params: el.getAttribute(\"data-reactive-params-param\"),\n debounce: el.getAttribute(\"data-reactive-debounce-param\"),\n throttle: el.getAttribute(\"data-reactive-throttle-param\"),\n confirm: el.getAttribute(\"data-reactive-confirm-param\"),\n optimistic: el.getAttribute(\"data-reactive-optimistic-param\"),\n loading: el.getAttribute(\"data-reactive-loading-param\"),\n shortcut: el.getAttribute(\"data-reactive-shortcut-param\"),\n }))\n}\n\n// The Stimulus data-action descriptor is \"event->controller#method\"; the event\n// is the part before \"->\" (a bare \"controller#method\" has an implicit default\n// event, which we report as null).\nfunction eventOf(dataAction) {\n if (!dataAction) return null\n const arrow = dataAction.indexOf(\"->\")\n return arrow === -1 ? null : dataAction.slice(0, arrow)\n}\n\n// Client-only op triggers ([data-reactive-ops-param]) — a menu toggle, a DOM op\n// chain that never round-trips. Issue #271: the attr holds one on_client\n// binding record per call ({on, window?, ops, …}, space-joined when mix-ed),\n// reported as one { event, ops } entry each. A legacy [[op, args]] list keeps\n// the data-action prefix as its event; an undecodable attr is reported raw.\nfunction ownedClientOps(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-ops-param]\", allRoots).flatMap((el) => {\n const raw = el.getAttribute(\"data-reactive-ops-param\")\n const event = eventOf(el.getAttribute(\"data-action\"))\n const legacy = parseJson(raw)\n if (Array.isArray(legacy)) return [{ event, ops: legacy }]\n const records = (raw ?? \"\").split(\" \").map(parseJson).filter((r) => r && typeof r === \"object\" && !Array.isArray(r))\n if (records.length === 0) return [{ event, ops: raw }]\n // A shortcut record (issue #357) reads as \"keydown@window shortcut=mod+z\".\n const shortcutOf = (s) => (typeof s === \"object\" ? `${s.keys}${s.inEditable ? \" editable\" : \"\"}` : s)\n const summary = (r) => `${r.on}${r.window ? \"@window\" : \"\"}${r.shortcut == null ? \"\" : ` shortcut=${shortcutOf(r.shortcut)}`}`\n return records.map((r) => ({ event: r.on ? summary(r) : event, ops: r.ops }))\n })\n}\n\n// JSON.parse that answers undefined instead of throwing — a malformed attr\n// must never break the scan.\nfunction parseJson(raw) {\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n}\n\n// Compute reducers bound on this root ([data-reactive-compute-reducer-param]) —\n// the client-side data-binding twins of reactive_compute. Report reducer names.\nfunction ownedComputes(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-compute-reducer-param]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-compute-reducer-param\"),\n )\n}\n\n// The named form controls the dispatch would collect and send — the same\n// selector the controller's field walk uses. Names only, never values.\nfunction ownedFields(root, allRoots) {\n return scopedQuery(root, \"input[name], select[name], textarea[name]\", allRoots).map((el) =>\n el.getAttribute(\"name\"),\n )\n}\n\n// reactive_show bindings: which owned field controls each element's visibility.\nfunction ownedShow(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-show-field]\", allRoots).map((el) => ({\n field: el.getAttribute(\"data-reactive-show-field\"),\n equals: el.getAttribute(\"data-reactive-show-equals\"),\n not: el.getAttribute(\"data-reactive-show-not\"),\n in: el.getAttribute(\"data-reactive-show-in\"),\n }))\n}\n\n// reactive_text spans (data-reactive-text=<name>) the client writes via\n// textContent — report the binding names.\nfunction ownedText(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-text]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-text\"),\n )\n}\n\n// reactive_filter lives on the ROOT itself (the input/option/group/empty\n// selectors it narrows client-side). Null when the root declares no filter.\nfunction readFilter(el) {\n const input = el.getAttribute(\"data-reactive-filter-input\")\n if (!input) return null\n return {\n input,\n option: el.getAttribute(\"data-reactive-filter-option\"),\n group: el.getAttribute(\"data-reactive-filter-group\"),\n empty: el.getAttribute(\"data-reactive-filter-empty\"),\n }\n}\n"
5
+ "// The on-demand client inspector (issue #168) — the browser half of the\n// debugging toolkit, the client twin of `phlex_reactive:actions` / the MCP tools.\n//\n// It is a STANDALONE module (the confirm.js / compute.js precedent): it does NOT\n// touch the hot-path reactive_controller.js and costs NOTHING until you import\n// it. Loaded on demand from the console (the engine pins it), it scans the live\n// DOM and maps every reactive root + bound trigger back to the server\n// Component#action names — so you can answer \"what's reactive on this page, what\n// does each control POST, and which component owns it?\" without reading source.\n//\n// (await import(\"phlex/reactive/inspect\")).report() // console.table\n// const roots = (await import(\"phlex/reactive/inspect\")).scan() // data\n//\n// The server↔client mapping is BY NAME: scan()'s `component` + each trigger's\n// `action` are exactly the Component#action identifiers `phlex_reactive:actions`\n// and the MCP tools list. The docs page shows the two side by side.\n//\n// It reads the SAME DOM contract reactive_controller.js writes (roots\n// [data-controller~=\"reactive\"] with id + data-reactive-token-value; triggers\n// [data-reactive-action-param] with data-action/params/modifiers; client-only\n// [data-reactive-ops-param]; computes [data-reactive-compute-reducer-param]; the\n// show/filter/text binding families). It never mutates anything — pure read.\n\n// Scan `root` (default the whole document) and return one entry per reactive\n// root, deepest-scoped: each trigger is attributed to its NEAREST reactive root,\n// so a nested root doesn't double-count its parent's triggers.\nexport function scan(root = document) {\n const roots = Array.from(root.querySelectorAll('[data-controller~=\"reactive\"]'))\n return roots.map((el) => inspectRoot(el, roots))\n}\n\n// console.table the inventory (roots + a flattened trigger table), and return\n// the same data scan() does so `report()` is also usable programmatically. Safe\n// on an empty document (logs an empty table, throws nothing).\nexport function report(root = document) {\n const roots = scan(root)\n const console = globalThis.console\n\n // Guard each console method independently — a host page (or a test harness)\n // may ship a partial console, and report() must never throw over logging.\n if (console && typeof console.log === \"function\") {\n console.log(`[phlex-reactive] ${roots.length} reactive root(s)`)\n }\n if (console && typeof console.table === \"function\") {\n console.table(\n roots.map((r) => ({\n id: r.id,\n component: r.component ?? (r.opaque ? \"(opaque token)\" : null),\n actions: r.triggers.map((t) => t.action).join(\", \"),\n fields: r.fields.join(\", \"),\n computes: r.computes.join(\", \"),\n })),\n )\n }\n return roots\n}\n\n// --- one root --------------------------------------------------------------\n\nfunction inspectRoot(el, allRoots) {\n const token = el.getAttribute(\"data-reactive-token-value\")\n const payload = decodeToken(token)\n\n return {\n id: el.id || null,\n component: payload.opaque ? null : (payload.c ?? null),\n gid: payload.gid ?? null,\n stateKeys: payload.s ? Object.keys(payload.s) : [],\n tokenVersion: payload.v ?? null,\n opaque: !!payload.opaque,\n status: readStatus(el),\n triggers: ownedTriggers(el, allRoots),\n clientOps: ownedClientOps(el, allRoots),\n computes: ownedComputes(el, allRoots),\n fields: ownedFields(el, allRoots),\n show: ownedShow(el, allRoots),\n text: ownedText(el, allRoots),\n filter: readFilter(el),\n }\n}\n\n// The signed identity is base64(JSON)--signature under the Rails 7.1+ JSON\n// message serializer. Decode the segment BEFORE the \"--\", JSON-parse it, and\n// unwrap the MessageVerifier envelope ({_rails:{data:…}}) to the {c,gid,s,v}\n// payload. Degrade to { opaque: true } on anything that isn't JSON-decodable —\n// a Marshal-serialized payload (older apps) simply isn't readable client-side,\n// and that's fine: the inspector shows \"(opaque token)\" rather than throwing.\nfunction decodeToken(token) {\n if (!token) return { opaque: true }\n\n try {\n const segment = token.split(\"--\")[0]\n const json = atobUtf8(segment)\n const parsed = JSON.parse(json)\n const data = parsed && parsed._rails && parsed._rails.data ? parsed._rails.data : parsed\n if (!data || typeof data !== \"object\") return { opaque: true }\n return data\n } catch {\n return { opaque: true }\n }\n}\n\n// base64 → string. Uses the platform atob; a malformed/urlsafe segment throws,\n// which decodeToken catches and reports as opaque.\nfunction atobUtf8(b64) {\n const decode = globalThis.atob || ((s) => Buffer.from(s, \"base64\").toString(\"binary\"))\n return decode(b64)\n}\n\n// The at-a-glance runtime state the controller stamps on the root. Names/flags\n// only — never a value that could be sensitive.\nfunction readStatus(el) {\n return {\n error: el.getAttribute(\"data-reactive-error\"),\n busy: el.getAttribute(\"aria-busy\") === \"true\" || el.hasAttribute(\"data-reactive-busy\"),\n dirty: el.hasAttribute(\"data-reactive-dirty\"),\n }\n}\n\n// --- ownership scoping -----------------------------------------------------\n//\n// An element belongs to a root when its NEAREST reactive-root ancestor is that\n// root (matching the controller's own ownership walk). closest() finds the\n// nearest ancestor matching the selector; the element itself can be the root\n// (for a root that also carries a binding), so we start the walk from the\n// element and accept `el` as its own owner.\n\nconst ROOT_SELECTOR = '[data-controller~=\"reactive\"]'\n\nfunction ownedBy(node, root) {\n return node.closest(ROOT_SELECTOR) === root\n}\n\n// Query `root` for `selector`, keeping only elements whose nearest reactive\n// root IS `root` — so a nested root's matches are excluded here and attributed\n// to that inner root instead.\nfunction scopedQuery(root, selector, allRoots) {\n return Array.from(root.querySelectorAll(selector)).filter((node) => ownedBy(node, root))\n}\n\n// --- the binding families --------------------------------------------------\n\nfunction ownedTriggers(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-action-param]\", allRoots).map((el) => ({\n action: el.getAttribute(\"data-reactive-action-param\"),\n event: eventOf(el.getAttribute(\"data-action\")),\n params: el.getAttribute(\"data-reactive-params-param\"),\n debounce: el.getAttribute(\"data-reactive-debounce-param\"),\n throttle: el.getAttribute(\"data-reactive-throttle-param\"),\n confirm: el.getAttribute(\"data-reactive-confirm-param\"),\n optimistic: el.getAttribute(\"data-reactive-optimistic-param\"),\n loading: el.getAttribute(\"data-reactive-loading-param\"),\n shortcut: el.getAttribute(\"data-reactive-shortcut-param\"),\n }))\n}\n\n// The Stimulus data-action descriptor is \"event->controller#method\"; the event\n// is the part before \"->\" (a bare \"controller#method\" has an implicit default\n// event, which we report as null).\nfunction eventOf(dataAction) {\n if (!dataAction) return null\n const arrow = dataAction.indexOf(\"->\")\n return arrow === -1 ? null : dataAction.slice(0, arrow)\n}\n\n// Client-only op triggers ([data-reactive-ops-param]) — a menu toggle, a DOM op\n// chain that never round-trips. Issue #271: the attr holds one on_client\n// binding record per call ({on, window?, ops, …}, space-joined when mix-ed),\n// reported as one { event, ops } entry each. A legacy [[op, args]] list keeps\n// the data-action prefix as its event; an undecodable attr is reported raw.\nfunction ownedClientOps(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-ops-param]\", allRoots).flatMap((el) => {\n const raw = el.getAttribute(\"data-reactive-ops-param\")\n const event = eventOf(el.getAttribute(\"data-action\"))\n const legacy = parseJson(raw)\n if (Array.isArray(legacy)) return [{ event, ops: legacy }]\n const records = (raw ?? \"\").split(\" \").map(parseJson).filter((r) => r && typeof r === \"object\" && !Array.isArray(r))\n if (records.length === 0) return [{ event, ops: raw }]\n // A shortcut record (issue #357) reads as \"keydown@window shortcut=mod+z\".\n const shortcutOf = (s) => (typeof s === \"object\" ? `${s.keys}${s.inEditable ? \" editable\" : \"\"}${s.handled ? \" handled\" : \"\"}` : s)\n const summary = (r) => `${r.on}${r.window ? \"@window\" : \"\"}${r.shortcut == null ? \"\" : ` shortcut=${shortcutOf(r.shortcut)}`}`\n return records.map((r) => ({ event: r.on ? summary(r) : event, ops: r.ops }))\n })\n}\n\n// JSON.parse that answers undefined instead of throwing — a malformed attr\n// must never break the scan.\nfunction parseJson(raw) {\n try {\n return JSON.parse(raw)\n } catch {\n return undefined\n }\n}\n\n// Compute reducers bound on this root ([data-reactive-compute-reducer-param]) —\n// the client-side data-binding twins of reactive_compute. Report reducer names.\nfunction ownedComputes(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-compute-reducer-param]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-compute-reducer-param\"),\n )\n}\n\n// The named form controls the dispatch would collect and send — the same\n// selector the controller's field walk uses. Names only, never values.\nfunction ownedFields(root, allRoots) {\n return scopedQuery(root, \"input[name], select[name], textarea[name]\", allRoots).map((el) =>\n el.getAttribute(\"name\"),\n )\n}\n\n// reactive_show bindings: which owned field controls each element's visibility.\nfunction ownedShow(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-show-field]\", allRoots).map((el) => ({\n field: el.getAttribute(\"data-reactive-show-field\"),\n equals: el.getAttribute(\"data-reactive-show-equals\"),\n not: el.getAttribute(\"data-reactive-show-not\"),\n in: el.getAttribute(\"data-reactive-show-in\"),\n }))\n}\n\n// reactive_text spans (data-reactive-text=<name>) the client writes via\n// textContent — report the binding names.\nfunction ownedText(root, allRoots) {\n return scopedQuery(root, \"[data-reactive-text]\", allRoots).map((el) =>\n el.getAttribute(\"data-reactive-text\"),\n )\n}\n\n// reactive_filter lives on the ROOT itself (the input/option/group/empty\n// selectors it narrows client-side, and how it matches: issue #425's mode and\n// fold). Null when the root declares no filter.\nfunction readFilter(el) {\n const input = el.getAttribute(\"data-reactive-filter-input\")\n if (!input) return null\n return {\n input,\n option: el.getAttribute(\"data-reactive-filter-option\"),\n group: el.getAttribute(\"data-reactive-filter-group\"),\n empty: el.getAttribute(\"data-reactive-filter-empty\"),\n match: el.getAttribute(\"data-reactive-filter-match\") ?? \"substring\",\n fold: el.hasAttribute(\"data-reactive-filter-fold\"),\n }\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "B4494A595CB8588264756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "0503DAE97DCB3C7564756E2164756E21",
9
9
  "names": []
10
10
  }