diff --git a/.gitignore b/.gitignore index 31201fa7..df4d934b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ apm_modules/ # StoryFlow local config .storyflow/ +.superpowers/ diff --git a/_config/dev.yml b/_config/dev.yml index 45769772..5cbd6979 100644 --- a/_config/dev.yml +++ b/_config/dev.yml @@ -164,3 +164,9 @@ WeDevelop\Grid\Dev\FixtureLoader: # via user actions (publish, edit, publish again) so each version # in the resulting history is the validated result of a real # interaction, not a fixture side-effect. + inspect-mode: + path: 'wedevelopnl/silverstripe-grid:tests/E2E/Fixture/inspect-mode.yml' + post_actions: + - action: publish_recursive + class: Page + identifier: e2e_inspect_page diff --git a/_config/grid-preview-inspector.yml b/_config/grid-preview-inspector.yml new file mode 100644 index 00000000..30004f40 --- /dev/null +++ b/_config/grid-preview-inspector.yml @@ -0,0 +1,9 @@ +--- +Name: grid-preview-inspector +After: + - '#grid-cms' +--- + +SilverStripe\CMS\Controllers\ContentController: + extensions: + - WeDevelop\Grid\Extensions\PreviewInspectorExtension diff --git a/client/dist/js/bundle.js b/client/dist/js/bundle.js index 9769e630..eaaae391 100644 --- a/client/dist/js/bundle.js +++ b/client/dist/js/bundle.js @@ -1,5 +1,43 @@ -(function(e,t,n){var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),u=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;lt[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e},d=(e,t,n)=>(n=e==null?{}:r(s(e)),u(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e));let f=d(e,1);e=d(e);function p(e,t=`error`){let n=window.ss?.store;if(n===void 0){console.warn(`[GridEditor] ${t}: ${e}`);return}n.dispatch({type:`DISPLAY_TOAST`,payload:{id:`toast-${crypto.randomUUID()}`,text:e,type:t,stay:t!==`success`}})}var m=(e,t)=>Object.entries(t).reduce((e,[t,n])=>e.replaceAll(`{${t}}`,String(n)),e),h=(e,t,n)=>{if(typeof window>`u`||!window.ss?.i18n)return n?m(t,n):t;let r=window.ss.i18n._t(e,t);return n?window.ss.i18n.inject(r,n):r},g=l(((t,n)=>{n.exports={...e}})),_=l((e=>{var t=g(),n=Symbol.for(`react.element`),r=Symbol.for(`react.fragment`),i=Object.prototype.hasOwnProperty,a=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,o={key:!0,ref:!0,__self:!0,__source:!0};function s(e,t,r){var s,c={},l=null,u=null;for(s in r!==void 0&&(l=``+r),t.key!==void 0&&(l=``+t.key),t.ref!==void 0&&(u=t.ref),t)i.call(t,s)&&!o.hasOwnProperty(s)&&(c[s]=t[s]);if(e&&e.defaultProps)for(s in t=e.defaultProps,t)c[s]===void 0&&(c[s]=t[s]);return{$$typeof:n,type:e,key:l,ref:u,props:c,_owner:a.current}}e.Fragment=r,e.jsx=s,e.jsxs=s})),v=l(((e,t)=>{t.exports=_()}))(),y=class extends e.Component{state={hasError:!1};static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,t){console.error(`[GridEditor] Render error:`,e,t),p(h(`WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR`,`The grid editor encountered an error and could not render.`))}render(){return this.state.hasError?(0,v.jsx)(`p`,{className:`grid-editor__error`,children:h(`WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK`,`The grid editor failed to render. Try reloading the page.`)}):this.props.children}},b=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},x=new class extends b{#e;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e();return window.addEventListener(`visibilitychange`,t,!1),()=>{window.removeEventListener(`visibilitychange`,t)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(e=>{typeof e==`boolean`?this.setFocused(e):this.onFocus()})}setFocused(e){this.#e!==e&&(this.#e=e,this.onFocus())}onFocus(){let e=this.isFocused();this.listeners.forEach(t=>{t(e)})}isFocused(){return typeof this.#e==`boolean`?this.#e:globalThis.document?.visibilityState!==`hidden`}},S={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},C=new class{#e=S;setTimeoutProvider(e){this.#e=e}setTimeout(e,t){return this.#e.setTimeout(e,t)}clearTimeout(e){this.#e.clearTimeout(e)}setInterval(e,t){return this.#e.setInterval(e,t)}clearInterval(e){this.#e.clearInterval(e)}};function w(e){setTimeout(e,0)}var T=typeof window>`u`||`Deno`in globalThis;function E(){}function ee(e,t){return typeof e==`function`?e(t):e}function D(e){return typeof e==`number`&&e>=0&&e!==1/0}function O(e,t){return Math.max(e+(t||0)-Date.now(),0)}function k(e,t){return typeof e==`function`?e(t):e}function A(e,t){return typeof e==`function`?e(t):e}function te(e,t){let{type:n=`all`,exact:r,fetchStatus:i,predicate:a,queryKey:o,stale:s}=e;if(o){if(r){if(t.queryHash!==j(o,t.options))return!1}else if(!N(t.queryKey,o))return!1}if(n!==`all`){let e=t.isActive();if(n===`active`&&!e||n===`inactive`&&e)return!1}return!(typeof s==`boolean`&&t.isStale()!==s||i&&i!==t.state.fetchStatus||a&&!a(t))}function ne(e,t){let{exact:n,status:r,predicate:i,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(M(t.options.mutationKey)!==M(a))return!1}else if(!N(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||i&&!i(t))}function j(e,t){return(t?.queryKeyHashFn||M)(e)}function M(e){return JSON.stringify(e,(e,t)=>I(t)?Object.keys(t).sort().reduce((e,n)=>(e[n]=t[n],e),{}):t)}function N(e,t){return e===t?!0:typeof e==typeof t&&e&&t&&typeof e==`object`&&typeof t==`object`?Object.keys(t).every(n=>N(e[n],t[n])):!1}var re=Object.prototype.hasOwnProperty;function P(e,t,n=0){if(e===t)return e;if(n>500)return t;let r=ie(e)&&ie(t);if(!r&&!(I(e)&&I(t)))return t;let i=(r?e:Object.keys(e)).length,a=r?t:Object.keys(t),o=a.length,s=r?Array(o):{},c=0;for(let l=0;l{C.setTimeout(t,e)})}function z(e,t,n){return typeof n.structuralSharing==`function`?n.structuralSharing(e,t):n.structuralSharing===!1?t:P(e,t)}function B(e,t,n=0){let r=[...e,t];return n&&r.length>n?r.slice(1):r}function ae(e,t,n=0){let r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var V=Symbol();function H(e,t){return!e.queryFn&&t?.initialPromise?()=>t.initialPromise:!e.queryFn||e.queryFn===V?()=>Promise.reject(Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function oe(e,t){return typeof e==`function`?e(...t):!!e}function se(e,t,n){let r=!1,i;return Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(i??=t(),r?i:(r=!0,i.aborted?n():i.addEventListener(`abort`,n,{once:!0}),i))}),e}var ce=(()=>{let e=()=>T;return{isServer(){return e()},setIsServer(t){e=t}}})();function U(){let e,t,n=new Promise((n,r)=>{e=n,t=r});n.status=`pending`,n.catch(()=>{});function r(e){Object.assign(n,e),delete n.resolve,delete n.reject}return n.resolve=t=>{r({status:`fulfilled`,value:t}),e(t)},n.reject=e=>{r({status:`rejected`,reason:e}),t(e)},n}var le=w;function ue(){let e=[],t=0,n=e=>{e()},r=e=>{e()},i=le,a=r=>{t?e.push(r):i(()=>{n(r)})},o=()=>{let t=e;e=[],t.length&&i(()=>{r(()=>{t.forEach(e=>{n(e)})})})};return{batch:e=>{let n;t++;try{n=e()}finally{t--,t||o()}return n},batchCalls:e=>(...t)=>{a(()=>{e(...t)})},schedule:a,setNotifyFunction:e=>{n=e},setBatchNotifyFunction:e=>{r=e},setScheduler:e=>{i=e}}}var W=ue(),de=new class extends b{#e=!0;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e(!0),n=()=>e(!1);return window.addEventListener(`online`,t,!1),window.addEventListener(`offline`,n,!1),()=>{window.removeEventListener(`online`,t),window.removeEventListener(`offline`,n)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(this.setOnline.bind(this))}setOnline(e){this.#e!==e&&(this.#e=e,this.listeners.forEach(t=>{t(e)}))}isOnline(){return this.#e}};function fe(e){return Math.min(1e3*2**e,3e4)}function pe(e){return(e??`online`)===`online`?de.isOnline():!0}var me=class extends Error{constructor(e){super(`CancelledError`),this.revert=e?.revert,this.silent=e?.silent}};function he(e){let t=!1,n=0,r,i=U(),a=()=>i.status!==`pending`,o=t=>{if(!a()){let n=new me(t);f(n),e.onCancel?.(n)}},s=()=>{t=!0},c=()=>{t=!1},l=()=>x.isFocused()&&(e.networkMode===`always`||de.isOnline())&&e.canRun(),u=()=>pe(e.networkMode)&&e.canRun(),d=e=>{a()||(r?.(),i.resolve(e))},f=e=>{a()||(r?.(),i.reject(e))},p=()=>new Promise(t=>{r=e=>{(a()||l())&&t(e)},e.onPause?.()}).then(()=>{r=void 0,a()||e.onContinue?.()}),m=()=>{if(a())return;let r,i=n===0?e.initialPromise:void 0;try{r=i??e.fn()}catch(e){r=Promise.reject(e)}Promise.resolve(r).then(d).catch(r=>{if(a())return;let i=e.retry??(ce.isServer()?0:3),o=e.retryDelay??fe,s=typeof o==`function`?o(n,r):o,c=i===!0||typeof i==`number`&&nl()?void 0:p()).then(()=>{t?f(r):m()})})};return{promise:i,status:()=>i.status,cancel:o,continue:()=>(r?.(),i),cancelRetry:s,continueRetry:c,canStart:u,start:()=>(u()?m():p().then(m),i)}}var ge=class{#e;destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),D(this.gcTime)&&(this.#e=C.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(ce.isServer()?1/0:300*1e3))}clearGcTimeout(){this.#e!==void 0&&(C.clearTimeout(this.#e),this.#e=void 0)}},_e=class extends ge{#e;#t;#n;#r;#i;#a;#o;constructor(e){super(),this.#o=!1,this.#a=e.defaultOptions,this.setOptions(e.options),this.observers=[],this.#r=e.client,this.#n=this.#r.getQueryCache(),this.queryKey=e.queryKey,this.queryHash=e.queryHash,this.#e=be(this.options),this.state=e.state??this.#e,this.scheduleGc()}get meta(){return this.options.meta}get promise(){return this.#i?.promise}setOptions(e){if(this.options={...this.#a,...e},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){let e=be(this.options);e.data!==void 0&&(this.setState(ye(e.data,e.dataUpdatedAt)),this.#e=e)}}optionalRemove(){!this.observers.length&&this.state.fetchStatus===`idle`&&this.#n.remove(this)}setData(e,t){let n=z(this.state.data,e,this.options);return this.#c({data:n,type:`success`,dataUpdatedAt:t?.updatedAt,manual:t?.manual}),n}setState(e,t){this.#c({type:`setState`,state:e,setStateOptions:t})}cancel(e){let t=this.#i?.promise;return this.#i?.cancel(e),t?t.then(E).catch(E):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return this.#e}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(e=>A(e.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===V||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(e=>k(e.options.staleTime,this)===`static`):!1}isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(e=0){return this.state.data===void 0?!0:e===`static`?!1:this.state.isInvalidated?!0:!O(this.state.dataUpdatedAt,e)}onFocus(){this.observers.find(e=>e.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),this.#i?.continue()}onOnline(){this.observers.find(e=>e.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),this.#i?.continue()}addObserver(e){this.observers.includes(e)||(this.observers.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,query:this,observer:e}))}removeObserver(e){this.observers.includes(e)&&(this.observers=this.observers.filter(t=>t!==e),this.observers.length||(this.#i&&(this.#o||this.#s()?this.#i.cancel({revert:!0}):this.#i.cancelRetry()),this.scheduleGc()),this.#n.notify({type:`observerRemoved`,query:this,observer:e}))}getObserversCount(){return this.observers.length}#s(){return this.state.fetchStatus===`paused`&&this.state.status===`pending`}invalidate(){this.state.isInvalidated||this.#c({type:`invalidate`})}async fetch(e,t){if(this.state.fetchStatus!==`idle`&&this.#i?.status()!==`rejected`){if(this.state.data!==void 0&&t?.cancelRefetch)this.cancel({silent:!0});else if(this.#i)return this.#i.continueRetry(),this.#i.promise}if(e&&this.setOptions(e),!this.options.queryFn){let e=this.observers.find(e=>e.options.queryFn);e&&this.setOptions(e.options)}let n=new AbortController,r=e=>{Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(this.#o=!0,n.signal)})},i=()=>{let e=H(this.options,t),n=(()=>{let e={client:this.#r,queryKey:this.queryKey,meta:this.meta};return r(e),e})();return this.#o=!1,this.options.persister?this.options.persister(e,n,this):e(n)},a=(()=>{let e={fetchOptions:t,options:this.options,queryKey:this.queryKey,client:this.#r,state:this.state,fetchFn:i};return r(e),e})();this.options.behavior?.onFetch(a,this),this.#t=this.state,(this.state.fetchStatus===`idle`||this.state.fetchMeta!==a.fetchOptions?.meta)&&this.#c({type:`fetch`,meta:a.fetchOptions?.meta}),this.#i=he({initialPromise:t?.initialPromise,fn:a.fetchFn,onCancel:e=>{e instanceof me&&e.revert&&this.setState({...this.#t,fetchStatus:`idle`}),n.abort()},onFail:(e,t)=>{this.#c({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#c({type:`pause`})},onContinue:()=>{this.#c({type:`continue`})},retry:a.options.retry,retryDelay:a.options.retryDelay,networkMode:a.options.networkMode,canRun:()=>!0});try{let e=await this.#i.start();if(e===void 0)throw Error(`${this.queryHash} data is undefined`);return this.setData(e),this.#n.config.onSuccess?.(e,this),this.#n.config.onSettled?.(e,this.state.error,this),e}catch(e){if(e instanceof me){if(e.silent)return this.#i.promise;if(e.revert){if(this.state.data===void 0)throw e;return this.state.data}}throw this.#c({type:`error`,error:e}),this.#n.config.onError?.(e,this),this.#n.config.onSettled?.(this.state.data,e,this),e}finally{this.scheduleGc()}}#c(e){this.state=(t=>{switch(e.type){case`failed`:return{...t,fetchFailureCount:e.failureCount,fetchFailureReason:e.error};case`pause`:return{...t,fetchStatus:`paused`};case`continue`:return{...t,fetchStatus:`fetching`};case`fetch`:return{...t,...ve(t.data,this.options),fetchMeta:e.meta??null};case`success`:let n={...t,...ye(e.data,e.dataUpdatedAt),dataUpdateCount:t.dataUpdateCount+1,...!e.manual&&{fetchStatus:`idle`,fetchFailureCount:0,fetchFailureReason:null}};return this.#t=e.manual?n:void 0,n;case`error`:let r=e.error;return{...t,error:r,errorUpdateCount:t.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:t.fetchFailureCount+1,fetchFailureReason:r,fetchStatus:`idle`,status:`error`,isInvalidated:!0};case`invalidate`:return{...t,isInvalidated:!0};case`setState`:return{...t,...e.state}}})(this.state),W.batch(()=>{this.observers.forEach(e=>{e.onQueryUpdate()}),this.#n.notify({query:this,type:`updated`,action:e})})}};function ve(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:pe(t.networkMode)?`fetching`:`paused`,...e===void 0&&{error:null,status:`pending`}}}function ye(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:`success`}}function be(e){let t=typeof e.initialData==`function`?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt==`function`?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?`success`:`pending`,fetchStatus:`idle`}}var xe=class extends b{constructor(e,t){super(),this.options=t,this.#e=e,this.#s=null,this.#o=U(),this.bindMethods(),this.setOptions(t)}#e;#t=void 0;#n=void 0;#r=void 0;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p;#m=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(this.#t.addObserver(this),Ce(this.#t,this.options)?this.#h():this.updateResult(),this.#y())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return we(this.#t,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return we(this.#t,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#b(),this.#x(),this.#t.removeObserver(this)}setOptions(e){let t=this.options,n=this.#t;if(this.options=this.#e.defaultQueryOptions(e),this.options.enabled!==void 0&&typeof this.options.enabled!=`boolean`&&typeof this.options.enabled!=`function`&&typeof A(this.options.enabled,this.#t)!=`boolean`)throw Error(`Expected enabled to be a boolean or a callback that returns a boolean`);this.#S(),this.#t.setOptions(this.options),t._defaulted&&!F(this.options,t)&&this.#e.getQueryCache().notify({type:`observerOptionsUpdated`,query:this.#t,observer:this});let r=this.hasListeners();r&&Te(this.#t,n,this.options,t)&&this.#h(),this.updateResult(),r&&(this.#t!==n||A(this.options.enabled,this.#t)!==A(t.enabled,this.#t)||k(this.options.staleTime,this.#t)!==k(t.staleTime,this.#t))&&this.#g();let i=this.#_();r&&(this.#t!==n||A(this.options.enabled,this.#t)!==A(t.enabled,this.#t)||i!==this.#p)&&this.#v(i)}getOptimisticResult(e){let t=this.#e.getQueryCache().build(this.#e,e),n=this.createResult(t,e);return De(this,n)&&(this.#r=n,this.#a=this.options,this.#i=this.#t.state),n}getCurrentResult(){return this.#r}trackResult(e,t){return new Proxy(e,{get:(e,n)=>(this.trackProp(n),t?.(n),n===`promise`&&(this.trackProp(`data`),!this.options.experimental_prefetchInRender&&this.#o.status===`pending`&&this.#o.reject(Error(`experimental_prefetchInRender feature flag is not enabled`))),Reflect.get(e,n))})}trackProp(e){this.#m.add(e)}getCurrentQuery(){return this.#t}refetch({...e}={}){return this.fetch({...e})}fetchOptimistic(e){let t=this.#e.defaultQueryOptions(e),n=this.#e.getQueryCache().build(this.#e,t);return n.fetch().then(()=>this.createResult(n,t))}fetch(e){return this.#h({...e,cancelRefetch:e.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#r))}#h(e){this.#S();let t=this.#t.fetch(this.options,e);return e?.throwOnError||(t=t.catch(E)),t}#g(){this.#b();let e=k(this.options.staleTime,this.#t);if(ce.isServer()||this.#r.isStale||!D(e))return;let t=O(this.#r.dataUpdatedAt,e)+1;this.#d=C.setTimeout(()=>{this.#r.isStale||this.updateResult()},t)}#_(){return(typeof this.options.refetchInterval==`function`?this.options.refetchInterval(this.#t):this.options.refetchInterval)??!1}#v(e){this.#x(),this.#p=e,!(ce.isServer()||A(this.options.enabled,this.#t)===!1||!D(this.#p)||this.#p===0)&&(this.#f=C.setInterval(()=>{(this.options.refetchIntervalInBackground||x.isFocused())&&this.#h()},this.#p))}#y(){this.#g(),this.#v(this.#_())}#b(){this.#d!==void 0&&(C.clearTimeout(this.#d),this.#d=void 0)}#x(){this.#f!==void 0&&(C.clearInterval(this.#f),this.#f=void 0)}createResult(e,t){let n=this.#t,r=this.options,i=this.#r,a=this.#i,o=this.#a,s=e===n?this.#n:e.state,{state:c}=e,l={...c},u=!1,d;if(t._optimisticResults){let i=this.hasListeners(),a=!i&&Ce(e,t),o=i&&Te(e,n,t,r);(a||o)&&(l={...l,...ve(c.data,e.options)}),t._optimisticResults===`isRestoring`&&(l.fetchStatus=`idle`)}let{error:f,errorUpdatedAt:p,status:m}=l;d=l.data;let h=!1;if(t.placeholderData!==void 0&&d===void 0&&m===`pending`){let e;i?.isPlaceholderData&&t.placeholderData===o?.placeholderData?(e=i.data,h=!0):e=typeof t.placeholderData==`function`?t.placeholderData(this.#u?.state.data,this.#u):t.placeholderData,e!==void 0&&(m=`success`,d=z(i?.data,e,t),u=!0)}if(t.select&&d!==void 0&&!h)if(i&&d===a?.data&&t.select===this.#c)d=this.#l;else try{this.#c=t.select,d=t.select(d),d=z(i?.data,d,t),this.#l=d,this.#s=null}catch(e){this.#s=e}this.#s&&(f=this.#s,d=this.#l,p=Date.now(),m=`error`);let g=l.fetchStatus===`fetching`,_=m===`pending`,v=m===`error`,y=_&&g,b=d!==void 0,x={status:m,fetchStatus:l.fetchStatus,isPending:_,isSuccess:m===`success`,isError:v,isInitialLoading:y,isLoading:y,data:d,dataUpdatedAt:l.dataUpdatedAt,error:f,errorUpdatedAt:p,failureCount:l.fetchFailureCount,failureReason:l.fetchFailureReason,errorUpdateCount:l.errorUpdateCount,isFetched:e.isFetched(),isFetchedAfterMount:l.dataUpdateCount>s.dataUpdateCount||l.errorUpdateCount>s.errorUpdateCount,isFetching:g,isRefetching:g&&!_,isLoadingError:v&&!b,isPaused:l.fetchStatus===`paused`,isPlaceholderData:u,isRefetchError:v&&b,isStale:Ee(e,t),refetch:this.refetch,promise:this.#o,isEnabled:A(t.enabled,e)!==!1};if(this.options.experimental_prefetchInRender){let t=x.data!==void 0,r=x.status===`error`&&!t,i=e=>{r?e.reject(x.error):t&&e.resolve(x.data)},a=()=>{i(this.#o=x.promise=U())},o=this.#o;switch(o.status){case`pending`:e.queryHash===n.queryHash&&i(o);break;case`fulfilled`:(r||x.data!==o.value)&&a();break;case`rejected`:(!r||x.error!==o.reason)&&a();break}}return x}updateResult(){let e=this.#r,t=this.createResult(this.#t,this.options);this.#i=this.#t.state,this.#a=this.options,this.#i.data!==void 0&&(this.#u=this.#t),!F(t,e)&&(this.#r=t,this.#C({listeners:(()=>{if(!e)return!0;let{notifyOnChangeProps:t}=this.options,n=typeof t==`function`?t():t;if(n===`all`||!n&&!this.#m.size)return!0;let r=new Set(n??this.#m);return this.options.throwOnError&&r.add(`error`),Object.keys(this.#r).some(t=>{let n=t;return this.#r[n]!==e[n]&&r.has(n)})})()}))}#S(){let e=this.#e.getQueryCache().build(this.#e,this.options);if(e===this.#t)return;let t=this.#t;this.#t=e,this.#n=e.state,this.hasListeners()&&(t?.removeObserver(this),e.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#y()}#C(e){W.batch(()=>{e.listeners&&this.listeners.forEach(e=>{e(this.#r)}),this.#e.getQueryCache().notify({query:this.#t,type:`observerResultsUpdated`})})}};function Se(e,t){return A(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status===`error`&&t.retryOnMount===!1)}function Ce(e,t){return Se(e,t)||e.state.data!==void 0&&we(e,t,t.refetchOnMount)}function we(e,t,n){if(A(t.enabled,e)!==!1&&k(t.staleTime,e)!==`static`){let r=typeof n==`function`?n(e):n;return r===`always`||r!==!1&&Ee(e,t)}return!1}function Te(e,t,n,r){return(e!==t||A(r.enabled,e)===!1)&&(!n.suspense||e.state.status!==`error`)&&Ee(e,n)}function Ee(e,t){return A(t.enabled,e)!==!1&&e.isStaleByTime(k(t.staleTime,e))}function De(e,t){return!F(e.getCurrentResult(),t)}function Oe(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,a=t.state.data?.pages||[],o=t.state.data?.pageParams||[],s={pages:[],pageParams:[]},c=0,l=async()=>{let n=!1,l=e=>{se(e,()=>t.signal,()=>n=!0)},u=H(t.options,t.fetchOptions),d=async(e,r,i)=>{if(n)return Promise.reject();if(r==null&&e.pages.length)return Promise.resolve(e);let a=await u((()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?`backward`:`forward`,meta:t.options.meta};return l(e),e})()),{maxPages:o}=t.options,s=i?ae:B;return{pages:s(e.pages,a,o),pageParams:s(e.pageParams,r,o)}};if(i&&a.length){let e=i===`backward`,t=e?Ae:ke,n={pages:a,pageParams:o};s=await d(n,t(r,n),e)}else{let t=e??a.length;do{let e=c===0?o[0]??r.initialPageParam:ke(r,s);if(c>0&&e==null)break;s=await d(s,e),c++}while(ct.options.persister?.(l,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=l}}}function ke(e,{pages:t,pageParams:n}){let r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function Ae(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}var je=class extends ge{#e;#t;#n;#r;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#n=e.mutationCache,this.#t=[],this.state=e.state||Me(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#n.notify({type:`observerRemoved`,mutation:this,observer:e})}optionalRemove(){this.#t.length||(this.state.status===`pending`?this.scheduleGc():this.#n.remove(this))}continue(){return this.#r?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#i({type:`continue`})},n={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#r=he({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error(`No mutationFn found`)),onFail:(e,t)=>{this.#i({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#i({type:`pause`})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#n.canRun(this)});let r=this.state.status===`pending`,i=!this.#r.canStart();try{if(r)t();else{this.#i({type:`pending`,variables:e,isPaused:i}),this.#n.config.onMutate&&await this.#n.config.onMutate(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&this.#i({type:`pending`,context:t,variables:e,isPaused:i})}let a=await this.#r.start();return await this.#n.config.onSuccess?.(a,e,this.state.context,this,n),await this.options.onSuccess?.(a,e,this.state.context,n),await this.#n.config.onSettled?.(a,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(a,null,e,this.state.context,n),this.#i({type:`success`,data:a}),a}catch(t){try{await this.#n.config.onError?.(t,e,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onError?.(t,e,this.state.context,n)}catch(e){Promise.reject(e)}try{await this.#n.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onSettled?.(void 0,t,e,this.state.context,n)}catch(e){Promise.reject(e)}throw this.#i({type:`error`,error:t}),t}finally{this.#n.runNext(this)}}#i(e){this.state=(t=>{switch(e.type){case`failed`:return{...t,failureCount:e.failureCount,failureReason:e.error};case`pause`:return{...t,isPaused:!0};case`continue`:return{...t,isPaused:!1};case`pending`:return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:`pending`,variables:e.variables,submittedAt:Date.now()};case`success`:return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:`success`,isPaused:!1};case`error`:return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:`error`}}})(this.state),W.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#n.notify({mutation:this,type:`updated`,action:e})})}};function Me(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:`idle`,variables:void 0,submittedAt:0}}var Ne=class extends b{constructor(e={}){super(),this.config=e,this.#e=new Set,this.#t=new Map,this.#n=0}#e;#t;#n;build(e,t,n){let r=new je({client:e,mutationCache:this,mutationId:++this.#n,options:e.defaultMutationOptions(t),state:n});return this.add(r),r}add(e){this.#e.add(e);let t=Pe(e);if(typeof t==`string`){let n=this.#t.get(t);n?n.push(e):this.#t.set(t,[e])}this.notify({type:`added`,mutation:e})}remove(e){if(this.#e.delete(e)){let t=Pe(e);if(typeof t==`string`){let n=this.#t.get(t);if(n)if(n.length>1){let t=n.indexOf(e);t!==-1&&n.splice(t,1)}else n[0]===e&&this.#t.delete(t)}}this.notify({type:`removed`,mutation:e})}canRun(e){let t=Pe(e);if(typeof t==`string`){let n=this.#t.get(t)?.find(e=>e.state.status===`pending`);return!n||n===e}else return!0}runNext(e){let t=Pe(e);return typeof t==`string`?(this.#t.get(t)?.find(t=>t!==e&&t.state.isPaused))?.continue()??Promise.resolve():Promise.resolve()}clear(){W.batch(()=>{this.#e.forEach(e=>{this.notify({type:`removed`,mutation:e})}),this.#e.clear(),this.#t.clear()})}getAll(){return Array.from(this.#e)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>ne(t,e))}findAll(e={}){return this.getAll().filter(t=>ne(e,t))}notify(e){W.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return W.batch(()=>Promise.all(e.map(e=>e.continue().catch(E))))}};function Pe(e){return e.options.scope?.id}var Fe=class extends b{#e;#t=void 0;#n;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#i()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),F(this.options,t)||this.#e.getMutationCache().notify({type:`observerOptionsUpdated`,mutation:this.#n,observer:this}),t?.mutationKey&&this.options.mutationKey&&M(t.mutationKey)!==M(this.options.mutationKey)?this.reset():this.#n?.state.status===`pending`&&this.#n.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#n?.removeObserver(this)}onMutationUpdate(e){this.#i(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#n?.removeObserver(this),this.#n=void 0,this.#i(),this.#a()}mutate(e,t){return this.#r=t,this.#n?.removeObserver(this),this.#n=this.#e.getMutationCache().build(this.#e,this.options),this.#n.addObserver(this),this.#n.execute(e)}#i(){let e=this.#n?.state??Me();this.#t={...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset}}#a(e){W.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,n=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{this.#r.onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{this.#r.onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},Ie=class extends b{constructor(e={}){super(),this.config=e,this.#e=new Map}#e;build(e,t,n){let r=t.queryKey,i=t.queryHash??j(r,t),a=this.get(i);return a||(a=new _e({client:e,queryKey:r,queryHash:i,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(r)}),this.add(a)),a}add(e){this.#e.has(e.queryHash)||(this.#e.set(e.queryHash,e),this.notify({type:`added`,query:e}))}remove(e){let t=this.#e.get(e.queryHash);t&&(e.destroy(),t===e&&this.#e.delete(e.queryHash),this.notify({type:`removed`,query:e}))}clear(){W.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#e.get(e)}getAll(){return[...this.#e.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>te(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>te(e,t)):t}notify(e){W.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){W.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){W.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},Le=class{#e;#t;#n;#r;#i;#a;#o;#s;constructor(e={}){this.#e=e.queryCache||new Ie,this.#t=e.mutationCache||new Ne,this.#n=e.defaultOptions||{},this.#r=new Map,this.#i=new Map,this.#a=0}mount(){this.#a++,this.#a===1&&(this.#o=x.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onFocus())}),this.#s=de.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onOnline())}))}unmount(){this.#a--,this.#a===0&&(this.#o?.(),this.#o=void 0,this.#s?.(),this.#s=void 0)}isFetching(e){return this.#e.findAll({...e,fetchStatus:`fetching`}).length}isMutating(e){return this.#t.findAll({...e,status:`pending`}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=this.#e.build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(k(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return this.#e.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=this.#e.get(r.queryHash)?.state.data,a=ee(t,i);if(a!==void 0)return this.#e.build(this,r).setData(a,{...n,manual:!0})}setQueriesData(e,t,n){return W.batch(()=>this.#e.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state}removeQueries(e){let t=this.#e;W.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=this.#e;return W.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:`active`,...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t},r=W.batch(()=>this.#e.findAll(e).map(e=>e.cancel(n)));return Promise.all(r).then(E).catch(E)}invalidateQueries(e,t={}){return W.batch(()=>(this.#e.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType===`none`?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??`active`},t)))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0},r=W.batch(()=>this.#e.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(E)),e.state.fetchStatus===`paused`?Promise.resolve():t}));return Promise.all(r).then(E)}fetchQuery(e){let t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);let n=this.#e.build(this,t);return n.isStaleByTime(k(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(E).catch(E)}fetchInfiniteQuery(e){return e.behavior=Oe(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(E).catch(E)}ensureInfiniteQueryData(e){return e.behavior=Oe(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return de.isOnline()?this.#t.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#e}getMutationCache(){return this.#t}getDefaultOptions(){return this.#n}setDefaultOptions(e){this.#n=e}setQueryDefaults(e,t){this.#r.set(M(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#r.values()],n={};return t.forEach(t=>{N(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){this.#i.set(M(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#i.values()],n={};return t.forEach(t=>{N(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#n.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||=j(t.queryKey,t),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!==`always`),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode=`offlineFirst`),t.queryFn===V&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#n.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#e.clear(),this.#t.clear()}},Re=f.createContext(void 0),ze=e=>{let t=f.useContext(Re);if(e)return e;if(!t)throw Error(`No QueryClient set, use QueryClientProvider to set one`);return t},Be=({client:e,children:t})=>(f.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,v.jsx)(Re.Provider,{value:e,children:t})),Ve=f.createContext(!1),He=()=>f.useContext(Ve);Ve.Provider;function Ue(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var We=f.createContext(Ue()),Ge=()=>f.useContext(We),Ke=(e,t,n)=>{let r=n?.state.error&&typeof e.throwOnError==`function`?oe(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},qe=e=>{f.useEffect(()=>{e.clearReset()},[e])},Je=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:i})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(i&&e.data===void 0||oe(n,[e.error,r])),Ye=e=>{if(e.suspense){let t=1e3,n=e=>e===`static`?e:Math.max(e??t,t),r=e.staleTime;e.staleTime=typeof r==`function`?(...e)=>n(r(...e)):n(r),typeof e.gcTime==`number`&&(e.gcTime=Math.max(e.gcTime,t))}},Xe=(e,t)=>e.isLoading&&e.isFetching&&!t,Ze=(e,t)=>e?.suspense&&t.isPending,Qe=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function $e(e,t,n){let r=He(),i=Ge(),a=ze(n),o=a.defaultQueryOptions(e);a.getDefaultOptions().queries?._experimental_beforeQuery?.(o);let s=a.getQueryCache().get(o.queryHash);o._optimisticResults=r?`isRestoring`:`optimistic`,Ye(o),Ke(o,i,s),qe(i);let c=!a.getQueryCache().get(o.queryHash),[l]=f.useState(()=>new t(a,o)),u=l.getOptimisticResult(o),d=!r&&e.subscribed!==!1;if(f.useSyncExternalStore(f.useCallback(e=>{let t=d?l.subscribe(W.batchCalls(e)):E;return l.updateResult(),t},[l,d]),()=>l.getCurrentResult(),()=>l.getCurrentResult()),f.useEffect(()=>{l.setOptions(o)},[o,l]),Ze(o,u))throw Qe(o,l,i);if(Je({result:u,errorResetBoundary:i,throwOnError:o.throwOnError,query:s,suspense:o.suspense}))throw u.error;return a.getDefaultOptions().queries?._experimental_afterQuery?.(o,u),o.experimental_prefetchInRender&&!ce.isServer()&&Xe(u,r)&&(c?Qe(o,l,i):s?.promise)?.catch(E).finally(()=>{l.updateResult()}),o.notifyOnChangeProps?u:l.trackResult(u)}function et(e,t){return $e(e,xe,t)}function tt(e,t){let n=ze(t),[r]=f.useState(()=>new Fe(n,e));f.useEffect(()=>{r.setOptions(e)},[r,e]);let i=f.useSyncExternalStore(f.useCallback(e=>r.subscribe(W.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=f.useCallback((e,t)=>{r.mutate(e,t).catch(E)},[r]);if(i.error&&oe(r.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:a,mutateAsync:i.mutate}}function nt({children:t}){let[n]=(0,e.useState)(()=>new Le({defaultOptions:{queries:{staleTime:3e4,gcTime:5*6e4,refetchOnWindowFocus:!1,retry:!1}}}));return(0,v.jsx)(Be,{client:n,children:t})}function rt(){if(window.Injector?.default===void 0)throw TypeError(`SilverStripe Injector is not available. Ensure the admin bundle is loaded before the grid editor.`);return window.Injector.default}function it(e,t){if(typeof window.Injector?.loadComponent!=`function`)throw TypeError(`SilverStripe Injector is not available. Ensure the admin bundle is loaded before the grid editor.`);return window.Injector.loadComponent(e,t)}var at=`.grid-editor__container`,ot=`data-grid-editor-mounted`,st=new WeakMap;function ct(e){let t=typeof e==`object`&&e?e:{},n=t[`grid-page-id`],r=t[`grid-zone`],i=t[`grid-readonly`],a=t[`grid-version`];return{pageId:typeof n==`number`?n:null,zone:typeof r==`string`&&r!==``?r:`main`,readonly:i===!0,version:typeof a==`number`?a:void 0}}function lt(e){let t=e.getAttribute(`data-schema`);if(t===null||t===``)return null;try{return JSON.parse(t)}catch{return null}}function ut(n,r){if(!st.has(n))try{let i=it(`GridEditor`),{pageId:a,zone:o,readonly:s,version:c}=ct(r),l=(0,t.createRoot)(n);st.set(n,l),n.setAttribute(ot,`true`),l.render((0,e.createElement)(nt,null,(0,e.createElement)(y,null,(0,e.createElement)(i,{pageId:a,zone:o,readonly:s,version:c}))))}catch(e){console.warn(`[GridEditor] Failed to mount grid editor.`,e)}}function dt(e){let t=st.get(e);if(t!==void 0){st.delete(e),e.removeAttribute(ot);try{t.unmount()}catch(e){console.warn(`[GridEditor] Error during unmount (element already detached).`,e)}}}function ft(){typeof window>`u`||window.jQuery?.entwine===void 0||window.jQuery.entwine(`ss`,e=>{e(`.js-injector-boot ${at}`).entwine({onmatch(){let e=this[0];ut(e,this.data(`schema`));let t=st.get(e)??null;this.setReactRoot(t)},onunmatch(){let e=this[0];dt(e),this.setReactRoot(null)}})})}function pt(){if(typeof document>`u`||typeof MutationObserver>`u`)return;let e=e=>{if(e instanceof HTMLElement){e.matches(at)&&ut(e,lt(e));for(let t of e.querySelectorAll(at))ut(t,lt(t))}},t=e=>{if(e instanceof HTMLElement){e.matches(at)&&dt(e);for(let t of e.querySelectorAll(at))dt(t)}};new MutationObserver(n=>{for(let r of n)r.addedNodes.forEach(e),r.removedNodes.forEach(t)}).observe(document.body,{childList:!0,subtree:!0});for(let e of document.querySelectorAll(at))ut(e,lt(e))}ft(),pt(),typeof window<`u`&&window.jQuery?.entwine!==void 0&&window.jQuery.entwine(`ss`,e=>{e(`.grid-settings-field .grid-settings-field__override-toggle`).entwine({onchange(){let t=e(this).closest(`tr`),n=e(this).is(`:checked`);t.find(`select, input`).not(`.grid-settings-field__override-toggle`).prop(`disabled`,!n),t.toggleClass(`is-overridden`,n)}})});function mt(){for(var t=arguments.length,n=Array(t),r=0;re=>{n.forEach(t=>t(e))},n)}var ht=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function gt(e){let t=Object.prototype.toString.call(e);return t===`[object Window]`||t===`[object global]`}function _t(e){return`nodeType`in e}function G(e){return e?gt(e)?e:_t(e)?e.ownerDocument?.defaultView??window:window:window}function vt(e){let{Document:t}=G(e);return e instanceof t}function yt(e){return gt(e)?!1:e instanceof G(e).HTMLElement}function bt(e){return e instanceof G(e).SVGElement}function xt(e){return e?gt(e)?e.document:_t(e)?vt(e)?e:yt(e)||bt(e)?e.ownerDocument:document:document:document}var K=ht?e.useLayoutEffect:e.useEffect;function St(t){let n=(0,e.useRef)(t);return K(()=>{n.current=t}),(0,e.useCallback)(function(){for(var e=arguments.length,t=Array(e),r=0;r{t.current=setInterval(e,n)},[]),(0,e.useCallback)(()=>{t.current!==null&&(clearInterval(t.current),t.current=null)},[])]}function wt(t,n){n===void 0&&(n=[t]);let r=(0,e.useRef)(t);return K(()=>{r.current!==t&&(r.current=t)},n),r}function Tt(t,n){let r=(0,e.useRef)();return(0,e.useMemo)(()=>{let e=t(r.current);return r.current=e,e},[...n])}function Et(t){let n=St(t),r=(0,e.useRef)(null);return[r,(0,e.useCallback)(e=>{e!==r.current&&n?.(e,r.current),r.current=e},[])]}function Dt(t){let n=(0,e.useRef)();return(0,e.useEffect)(()=>{n.current=t},[t]),n.current}var Ot={};function kt(t,n){return(0,e.useMemo)(()=>{if(n)return n;let e=Ot[t]==null?0:Ot[t]+1;return Ot[t]=e,t+`-`+e},[t,n])}function At(e){return function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i{let r=Object.entries(n);for(let[n,i]of r){let r=t[n];r!=null&&(t[n]=r+e*i)}return t},{...t})}}var jt=At(1),Mt=At(-1);function Nt(e){return`clientX`in e&&`clientY`in e}function Pt(e){if(!e)return!1;let{KeyboardEvent:t}=G(e.target);return t&&e instanceof t}function Ft(e){if(!e)return!1;let{TouchEvent:t}=G(e.target);return t&&e instanceof t}function It(e){if(Ft(e)){if(e.touches&&e.touches.length){let{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){let{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return Nt(e)?{x:e.clientX,y:e.clientY}:null}var Lt=Object.freeze({Translate:{toString(e){if(!e)return;let{x:t,y:n}=e;return`translate3d(`+(t?Math.round(t):0)+`px, `+(n?Math.round(n):0)+`px, 0)`}},Scale:{toString(e){if(!e)return;let{scaleX:t,scaleY:n}=e;return`scaleX(`+t+`) scaleY(`+n+`)`}},Transform:{toString(e){if(e)return[Lt.Translate.toString(e),Lt.Scale.toString(e)].join(` `)}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+` `+n+`ms `+r}}}),Rt=`a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]`;function zt(e){return e.matches(Rt)?e:e.querySelector(Rt)}var Bt={display:`none`};function Vt(t){let{id:n,value:r}=t;return e.default.createElement(`div`,{id:n,style:Bt},r)}function Ht(t){let{id:n,announcement:r,ariaLiveType:i=`assertive`}=t;return e.default.createElement(`div`,{id:n,style:{position:`fixed`,top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:`hidden`,clip:`rect(0 0 0 0)`,clipPath:`inset(100%)`,whiteSpace:`nowrap`},role:`status`,"aria-live":i,"aria-atomic":!0},r)}function Ut(){let[t,n]=(0,e.useState)(``);return{announce:(0,e.useCallback)(e=>{e!=null&&n(e)},[]),announcement:t}}var Wt=(0,e.createContext)(null);function Gt(t){let n=(0,e.useContext)(Wt);(0,e.useEffect)(()=>{if(!n)throw Error(`useDndMonitor must be used within a children of `);return n(t)},[t,n])}function Kt(){let[t]=(0,e.useState)(()=>new Set),n=(0,e.useCallback)(e=>(t.add(e),()=>t.delete(e)),[t]);return[(0,e.useCallback)(e=>{let{type:n,event:r}=e;t.forEach(e=>e[n]?.call(e,r))},[t]),n]}var qt={draggable:` +(function(e,t,n){var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),u=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;lt[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e},d=(e,t,n)=>(n=e==null?{}:r(s(e)),u(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e));let f=d(e,1);e=d(e);function p(e,t=`error`){let n=window.ss?.store;if(n===void 0){console.warn(`[GridEditor] ${t}: ${e}`);return}n.dispatch({type:`DISPLAY_TOAST`,payload:{id:`toast-${crypto.randomUUID()}`,text:e,type:t,stay:t!==`success`}})}var m=(e,t)=>Object.entries(t).reduce((e,[t,n])=>e.replaceAll(`{${t}}`,String(n)),e),h=(e,t,n)=>{if(typeof window>`u`||!window.ss?.i18n)return n?m(t,n):t;let r=window.ss.i18n._t(e,t);return n?window.ss.i18n.inject(r,n):r},g=l(((t,n)=>{n.exports={...e}})),_=l((e=>{var t=g(),n=Symbol.for(`react.element`),r=Symbol.for(`react.fragment`),i=Object.prototype.hasOwnProperty,a=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,o={key:!0,ref:!0,__self:!0,__source:!0};function s(e,t,r){var s,c={},l=null,u=null;for(s in r!==void 0&&(l=``+r),t.key!==void 0&&(l=``+t.key),t.ref!==void 0&&(u=t.ref),t)i.call(t,s)&&!o.hasOwnProperty(s)&&(c[s]=t[s]);if(e&&e.defaultProps)for(s in t=e.defaultProps,t)c[s]===void 0&&(c[s]=t[s]);return{$$typeof:n,type:e,key:l,ref:u,props:c,_owner:a.current}}e.Fragment=r,e.jsx=s,e.jsxs=s})),v=l(((e,t)=>{t.exports=_()}))(),y=class extends e.Component{state={hasError:!1};static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e,t){console.error(`[GridEditor] Render error:`,e,t),p(h(`WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR`,`The grid editor encountered an error and could not render.`))}render(){return this.state.hasError?(0,v.jsx)(`p`,{className:`grid-editor__error`,children:h(`WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK`,`The grid editor failed to render. Try reloading the page.`)}):this.props.children}},b=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(e){return this.listeners.add(e),this.onSubscribe(),()=>{this.listeners.delete(e),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}},x=new class extends b{#e;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e();return window.addEventListener(`visibilitychange`,t,!1),()=>{window.removeEventListener(`visibilitychange`,t)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(e=>{typeof e==`boolean`?this.setFocused(e):this.onFocus()})}setFocused(e){this.#e!==e&&(this.#e=e,this.onFocus())}onFocus(){let e=this.isFocused();this.listeners.forEach(t=>{t(e)})}isFocused(){return typeof this.#e==`boolean`?this.#e:globalThis.document?.visibilityState!==`hidden`}},S={setTimeout:(e,t)=>setTimeout(e,t),clearTimeout:e=>clearTimeout(e),setInterval:(e,t)=>setInterval(e,t),clearInterval:e=>clearInterval(e)},C=new class{#e=S;setTimeoutProvider(e){this.#e=e}setTimeout(e,t){return this.#e.setTimeout(e,t)}clearTimeout(e){this.#e.clearTimeout(e)}setInterval(e,t){return this.#e.setInterval(e,t)}clearInterval(e){this.#e.clearInterval(e)}};function w(e){setTimeout(e,0)}var T=typeof window>`u`||`Deno`in globalThis;function E(){}function ee(e,t){return typeof e==`function`?e(t):e}function D(e){return typeof e==`number`&&e>=0&&e!==1/0}function O(e,t){return Math.max(e+(t||0)-Date.now(),0)}function k(e,t){return typeof e==`function`?e(t):e}function A(e,t){return typeof e==`function`?e(t):e}function te(e,t){let{type:n=`all`,exact:r,fetchStatus:i,predicate:a,queryKey:o,stale:s}=e;if(o){if(r){if(t.queryHash!==j(o,t.options))return!1}else if(!N(t.queryKey,o))return!1}if(n!==`all`){let e=t.isActive();if(n===`active`&&!e||n===`inactive`&&e)return!1}return!(typeof s==`boolean`&&t.isStale()!==s||i&&i!==t.state.fetchStatus||a&&!a(t))}function ne(e,t){let{exact:n,status:r,predicate:i,mutationKey:a}=e;if(a){if(!t.options.mutationKey)return!1;if(n){if(M(t.options.mutationKey)!==M(a))return!1}else if(!N(t.options.mutationKey,a))return!1}return!(r&&t.state.status!==r||i&&!i(t))}function j(e,t){return(t?.queryKeyHashFn||M)(e)}function M(e){return JSON.stringify(e,(e,t)=>ae(t)?Object.keys(t).sort().reduce((e,n)=>(e[n]=t[n],e),{}):t)}function N(e,t){return e===t?!0:typeof e==typeof t&&e&&t&&typeof e==`object`&&typeof t==`object`?Object.keys(t).every(n=>N(e[n],t[n])):!1}var re=Object.prototype.hasOwnProperty;function P(e,t,n=0){if(e===t)return e;if(n>500)return t;let r=ie(e)&&ie(t);if(!r&&!(ae(e)&&ae(t)))return t;let i=(r?e:Object.keys(e)).length,a=r?t:Object.keys(t),o=a.length,s=r?Array(o):{},c=0;for(let l=0;l{C.setTimeout(t,e)})}function L(e,t,n){return typeof n.structuralSharing==`function`?n.structuralSharing(e,t):n.structuralSharing===!1?t:P(e,t)}function R(e,t,n=0){let r=[...e,t];return n&&r.length>n?r.slice(1):r}function se(e,t,n=0){let r=[t,...e];return n&&r.length>n?r.slice(0,-1):r}var ce=Symbol();function le(e,t){return!e.queryFn&&t?.initialPromise?()=>t.initialPromise:!e.queryFn||e.queryFn===ce?()=>Promise.reject(Error(`Missing queryFn: '${e.queryHash}'`)):e.queryFn}function ue(e,t){return typeof e==`function`?e(...t):!!e}function de(e,t,n){let r=!1,i;return Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(i??=t(),r?i:(r=!0,i.aborted?n():i.addEventListener(`abort`,n,{once:!0}),i))}),e}var fe=(()=>{let e=()=>T;return{isServer(){return e()},setIsServer(t){e=t}}})();function pe(){let e,t,n=new Promise((n,r)=>{e=n,t=r});n.status=`pending`,n.catch(()=>{});function r(e){Object.assign(n,e),delete n.resolve,delete n.reject}return n.resolve=t=>{r({status:`fulfilled`,value:t}),e(t)},n.reject=e=>{r({status:`rejected`,reason:e}),t(e)},n}var me=w;function he(){let e=[],t=0,n=e=>{e()},r=e=>{e()},i=me,a=r=>{t?e.push(r):i(()=>{n(r)})},o=()=>{let t=e;e=[],t.length&&i(()=>{r(()=>{t.forEach(e=>{n(e)})})})};return{batch:e=>{let n;t++;try{n=e()}finally{t--,t||o()}return n},batchCalls:e=>(...t)=>{a(()=>{e(...t)})},schedule:a,setNotifyFunction:e=>{n=e},setBatchNotifyFunction:e=>{r=e},setScheduler:e=>{i=e}}}var z=he(),ge=new class extends b{#e=!0;#t;#n;constructor(){super(),this.#n=e=>{if(typeof window<`u`&&window.addEventListener){let t=()=>e(!0),n=()=>e(!1);return window.addEventListener(`online`,t,!1),window.addEventListener(`offline`,n,!1),()=>{window.removeEventListener(`online`,t),window.removeEventListener(`offline`,n)}}}}onSubscribe(){this.#t||this.setEventListener(this.#n)}onUnsubscribe(){this.hasListeners()||(this.#t?.(),this.#t=void 0)}setEventListener(e){this.#n=e,this.#t?.(),this.#t=e(this.setOnline.bind(this))}setOnline(e){this.#e!==e&&(this.#e=e,this.listeners.forEach(t=>{t(e)}))}isOnline(){return this.#e}};function _e(e){return Math.min(1e3*2**e,3e4)}function ve(e){return(e??`online`)===`online`?ge.isOnline():!0}var ye=class extends Error{constructor(e){super(`CancelledError`),this.revert=e?.revert,this.silent=e?.silent}};function be(e){let t=!1,n=0,r,i=pe(),a=()=>i.status!==`pending`,o=t=>{if(!a()){let n=new ye(t);f(n),e.onCancel?.(n)}},s=()=>{t=!0},c=()=>{t=!1},l=()=>x.isFocused()&&(e.networkMode===`always`||ge.isOnline())&&e.canRun(),u=()=>ve(e.networkMode)&&e.canRun(),d=e=>{a()||(r?.(),i.resolve(e))},f=e=>{a()||(r?.(),i.reject(e))},p=()=>new Promise(t=>{r=e=>{(a()||l())&&t(e)},e.onPause?.()}).then(()=>{r=void 0,a()||e.onContinue?.()}),m=()=>{if(a())return;let r,i=n===0?e.initialPromise:void 0;try{r=i??e.fn()}catch(e){r=Promise.reject(e)}Promise.resolve(r).then(d).catch(r=>{if(a())return;let i=e.retry??(fe.isServer()?0:3),o=e.retryDelay??_e,s=typeof o==`function`?o(n,r):o,c=i===!0||typeof i==`number`&&nl()?void 0:p()).then(()=>{t?f(r):m()})})};return{promise:i,status:()=>i.status,cancel:o,continue:()=>(r?.(),i),cancelRetry:s,continueRetry:c,canStart:u,start:()=>(u()?m():p().then(m),i)}}var xe=class{#e;destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),D(this.gcTime)&&(this.#e=C.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(e){this.gcTime=Math.max(this.gcTime||0,e??(fe.isServer()?1/0:300*1e3))}clearGcTimeout(){this.#e!==void 0&&(C.clearTimeout(this.#e),this.#e=void 0)}},Se=class extends xe{#e;#t;#n;#r;#i;#a;#o;constructor(e){super(),this.#o=!1,this.#a=e.defaultOptions,this.setOptions(e.options),this.observers=[],this.#r=e.client,this.#n=this.#r.getQueryCache(),this.queryKey=e.queryKey,this.queryHash=e.queryHash,this.#e=Te(this.options),this.state=e.state??this.#e,this.scheduleGc()}get meta(){return this.options.meta}get promise(){return this.#i?.promise}setOptions(e){if(this.options={...this.#a,...e},this.updateGcTime(this.options.gcTime),this.state&&this.state.data===void 0){let e=Te(this.options);e.data!==void 0&&(this.setState(we(e.data,e.dataUpdatedAt)),this.#e=e)}}optionalRemove(){!this.observers.length&&this.state.fetchStatus===`idle`&&this.#n.remove(this)}setData(e,t){let n=L(this.state.data,e,this.options);return this.#c({data:n,type:`success`,dataUpdatedAt:t?.updatedAt,manual:t?.manual}),n}setState(e,t){this.#c({type:`setState`,state:e,setStateOptions:t})}cancel(e){let t=this.#i?.promise;return this.#i?.cancel(e),t?t.then(E).catch(E):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}get resetState(){return this.#e}reset(){this.destroy(),this.setState(this.resetState)}isActive(){return this.observers.some(e=>A(e.options.enabled,this)!==!1)}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===ce||!this.isFetched()}isFetched(){return this.state.dataUpdateCount+this.state.errorUpdateCount>0}isStatic(){return this.getObserversCount()>0?this.observers.some(e=>k(e.options.staleTime,this)===`static`):!1}isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.getCurrentResult().isStale):this.state.data===void 0||this.state.isInvalidated}isStaleByTime(e=0){return this.state.data===void 0?!0:e===`static`?!1:this.state.isInvalidated?!0:!O(this.state.dataUpdatedAt,e)}onFocus(){this.observers.find(e=>e.shouldFetchOnWindowFocus())?.refetch({cancelRefetch:!1}),this.#i?.continue()}onOnline(){this.observers.find(e=>e.shouldFetchOnReconnect())?.refetch({cancelRefetch:!1}),this.#i?.continue()}addObserver(e){this.observers.includes(e)||(this.observers.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,query:this,observer:e}))}removeObserver(e){this.observers.includes(e)&&(this.observers=this.observers.filter(t=>t!==e),this.observers.length||(this.#i&&(this.#o||this.#s()?this.#i.cancel({revert:!0}):this.#i.cancelRetry()),this.scheduleGc()),this.#n.notify({type:`observerRemoved`,query:this,observer:e}))}getObserversCount(){return this.observers.length}#s(){return this.state.fetchStatus===`paused`&&this.state.status===`pending`}invalidate(){this.state.isInvalidated||this.#c({type:`invalidate`})}async fetch(e,t){if(this.state.fetchStatus!==`idle`&&this.#i?.status()!==`rejected`){if(this.state.data!==void 0&&t?.cancelRefetch)this.cancel({silent:!0});else if(this.#i)return this.#i.continueRetry(),this.#i.promise}if(e&&this.setOptions(e),!this.options.queryFn){let e=this.observers.find(e=>e.options.queryFn);e&&this.setOptions(e.options)}let n=new AbortController,r=e=>{Object.defineProperty(e,`signal`,{enumerable:!0,get:()=>(this.#o=!0,n.signal)})},i=()=>{let e=le(this.options,t),n=(()=>{let e={client:this.#r,queryKey:this.queryKey,meta:this.meta};return r(e),e})();return this.#o=!1,this.options.persister?this.options.persister(e,n,this):e(n)},a=(()=>{let e={fetchOptions:t,options:this.options,queryKey:this.queryKey,client:this.#r,state:this.state,fetchFn:i};return r(e),e})();this.options.behavior?.onFetch(a,this),this.#t=this.state,(this.state.fetchStatus===`idle`||this.state.fetchMeta!==a.fetchOptions?.meta)&&this.#c({type:`fetch`,meta:a.fetchOptions?.meta}),this.#i=be({initialPromise:t?.initialPromise,fn:a.fetchFn,onCancel:e=>{e instanceof ye&&e.revert&&this.setState({...this.#t,fetchStatus:`idle`}),n.abort()},onFail:(e,t)=>{this.#c({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#c({type:`pause`})},onContinue:()=>{this.#c({type:`continue`})},retry:a.options.retry,retryDelay:a.options.retryDelay,networkMode:a.options.networkMode,canRun:()=>!0});try{let e=await this.#i.start();if(e===void 0)throw Error(`${this.queryHash} data is undefined`);return this.setData(e),this.#n.config.onSuccess?.(e,this),this.#n.config.onSettled?.(e,this.state.error,this),e}catch(e){if(e instanceof ye){if(e.silent)return this.#i.promise;if(e.revert){if(this.state.data===void 0)throw e;return this.state.data}}throw this.#c({type:`error`,error:e}),this.#n.config.onError?.(e,this),this.#n.config.onSettled?.(this.state.data,e,this),e}finally{this.scheduleGc()}}#c(e){let t=t=>{switch(e.type){case`failed`:return{...t,fetchFailureCount:e.failureCount,fetchFailureReason:e.error};case`pause`:return{...t,fetchStatus:`paused`};case`continue`:return{...t,fetchStatus:`fetching`};case`fetch`:return{...t,...Ce(t.data,this.options),fetchMeta:e.meta??null};case`success`:let n={...t,...we(e.data,e.dataUpdatedAt),dataUpdateCount:t.dataUpdateCount+1,...!e.manual&&{fetchStatus:`idle`,fetchFailureCount:0,fetchFailureReason:null}};return this.#t=e.manual?n:void 0,n;case`error`:let r=e.error;return{...t,error:r,errorUpdateCount:t.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:t.fetchFailureCount+1,fetchFailureReason:r,fetchStatus:`idle`,status:`error`,isInvalidated:!0};case`invalidate`:return{...t,isInvalidated:!0};case`setState`:return{...t,...e.state}}};this.state=t(this.state),z.batch(()=>{this.observers.forEach(e=>{e.onQueryUpdate()}),this.#n.notify({query:this,type:`updated`,action:e})})}};function Ce(e,t){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:ve(t.networkMode)?`fetching`:`paused`,...e===void 0&&{error:null,status:`pending`}}}function we(e,t){return{data:e,dataUpdatedAt:t??Date.now(),error:null,isInvalidated:!1,status:`success`}}function Te(e){let t=typeof e.initialData==`function`?e.initialData():e.initialData,n=t!==void 0,r=n?typeof e.initialDataUpdatedAt==`function`?e.initialDataUpdatedAt():e.initialDataUpdatedAt:0;return{data:t,dataUpdateCount:0,dataUpdatedAt:n?r??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:n?`success`:`pending`,fetchStatus:`idle`}}var Ee=class extends b{constructor(e,t){super(),this.options=t,this.#e=e,this.#s=null,this.#o=pe(),this.bindMethods(),this.setOptions(t)}#e;#t=void 0;#n=void 0;#r=void 0;#i;#a;#o;#s;#c;#l;#u;#d;#f;#p;#m=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){this.listeners.size===1&&(this.#t.addObserver(this),Oe(this.#t,this.options)?this.#h():this.updateResult(),this.#y())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return ke(this.#t,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return ke(this.#t,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#b(),this.#x(),this.#t.removeObserver(this)}setOptions(e){let t=this.options,n=this.#t;if(this.options=this.#e.defaultQueryOptions(e),this.options.enabled!==void 0&&typeof this.options.enabled!=`boolean`&&typeof this.options.enabled!=`function`&&typeof A(this.options.enabled,this.#t)!=`boolean`)throw Error(`Expected enabled to be a boolean or a callback that returns a boolean`);this.#S(),this.#t.setOptions(this.options),t._defaulted&&!F(this.options,t)&&this.#e.getQueryCache().notify({type:`observerOptionsUpdated`,query:this.#t,observer:this});let r=this.hasListeners();r&&Ae(this.#t,n,this.options,t)&&this.#h(),this.updateResult(),r&&(this.#t!==n||A(this.options.enabled,this.#t)!==A(t.enabled,this.#t)||k(this.options.staleTime,this.#t)!==k(t.staleTime,this.#t))&&this.#g();let i=this.#_();r&&(this.#t!==n||A(this.options.enabled,this.#t)!==A(t.enabled,this.#t)||i!==this.#p)&&this.#v(i)}getOptimisticResult(e){let t=this.#e.getQueryCache().build(this.#e,e),n=this.createResult(t,e);return Me(this,n)&&(this.#r=n,this.#a=this.options,this.#i=this.#t.state),n}getCurrentResult(){return this.#r}trackResult(e,t){return new Proxy(e,{get:(e,n)=>(this.trackProp(n),t?.(n),n===`promise`&&(this.trackProp(`data`),!this.options.experimental_prefetchInRender&&this.#o.status===`pending`&&this.#o.reject(Error(`experimental_prefetchInRender feature flag is not enabled`))),Reflect.get(e,n))})}trackProp(e){this.#m.add(e)}getCurrentQuery(){return this.#t}refetch({...e}={}){return this.fetch({...e})}fetchOptimistic(e){let t=this.#e.defaultQueryOptions(e),n=this.#e.getQueryCache().build(this.#e,t);return n.fetch().then(()=>this.createResult(n,t))}fetch(e){return this.#h({...e,cancelRefetch:e.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#r))}#h(e){this.#S();let t=this.#t.fetch(this.options,e);return e?.throwOnError||(t=t.catch(E)),t}#g(){this.#b();let e=k(this.options.staleTime,this.#t);if(fe.isServer()||this.#r.isStale||!D(e))return;let t=O(this.#r.dataUpdatedAt,e)+1;this.#d=C.setTimeout(()=>{this.#r.isStale||this.updateResult()},t)}#_(){return(typeof this.options.refetchInterval==`function`?this.options.refetchInterval(this.#t):this.options.refetchInterval)??!1}#v(e){this.#x(),this.#p=e,!(fe.isServer()||A(this.options.enabled,this.#t)===!1||!D(this.#p)||this.#p===0)&&(this.#f=C.setInterval(()=>{(this.options.refetchIntervalInBackground||x.isFocused())&&this.#h()},this.#p))}#y(){this.#g(),this.#v(this.#_())}#b(){this.#d!==void 0&&(C.clearTimeout(this.#d),this.#d=void 0)}#x(){this.#f!==void 0&&(C.clearInterval(this.#f),this.#f=void 0)}createResult(e,t){let n=this.#t,r=this.options,i=this.#r,a=this.#i,o=this.#a,s=e===n?this.#n:e.state,{state:c}=e,l={...c},u=!1,d;if(t._optimisticResults){let i=this.hasListeners(),a=!i&&Oe(e,t),o=i&&Ae(e,n,t,r);(a||o)&&(l={...l,...Ce(c.data,e.options)}),t._optimisticResults===`isRestoring`&&(l.fetchStatus=`idle`)}let{error:f,errorUpdatedAt:p,status:m}=l;d=l.data;let h=!1;if(t.placeholderData!==void 0&&d===void 0&&m===`pending`){let e;i?.isPlaceholderData&&t.placeholderData===o?.placeholderData?(e=i.data,h=!0):e=typeof t.placeholderData==`function`?t.placeholderData(this.#u?.state.data,this.#u):t.placeholderData,e!==void 0&&(m=`success`,d=L(i?.data,e,t),u=!0)}if(t.select&&d!==void 0&&!h)if(i&&d===a?.data&&t.select===this.#c)d=this.#l;else try{this.#c=t.select,d=t.select(d),d=L(i?.data,d,t),this.#l=d,this.#s=null}catch(e){this.#s=e}this.#s&&(f=this.#s,d=this.#l,p=Date.now(),m=`error`);let g=l.fetchStatus===`fetching`,_=m===`pending`,v=m===`error`,y=_&&g,b=d!==void 0,x={status:m,fetchStatus:l.fetchStatus,isPending:_,isSuccess:m===`success`,isError:v,isInitialLoading:y,isLoading:y,data:d,dataUpdatedAt:l.dataUpdatedAt,error:f,errorUpdatedAt:p,failureCount:l.fetchFailureCount,failureReason:l.fetchFailureReason,errorUpdateCount:l.errorUpdateCount,isFetched:e.isFetched(),isFetchedAfterMount:l.dataUpdateCount>s.dataUpdateCount||l.errorUpdateCount>s.errorUpdateCount,isFetching:g,isRefetching:g&&!_,isLoadingError:v&&!b,isPaused:l.fetchStatus===`paused`,isPlaceholderData:u,isRefetchError:v&&b,isStale:je(e,t),refetch:this.refetch,promise:this.#o,isEnabled:A(t.enabled,e)!==!1};if(this.options.experimental_prefetchInRender){let t=x.data!==void 0,r=x.status===`error`&&!t,i=e=>{r?e.reject(x.error):t&&e.resolve(x.data)},a=()=>{i(this.#o=x.promise=pe())},o=this.#o;switch(o.status){case`pending`:e.queryHash===n.queryHash&&i(o);break;case`fulfilled`:(r||x.data!==o.value)&&a();break;case`rejected`:(!r||x.error!==o.reason)&&a();break}}return x}updateResult(){let e=this.#r,t=this.createResult(this.#t,this.options);this.#i=this.#t.state,this.#a=this.options,this.#i.data!==void 0&&(this.#u=this.#t),!F(t,e)&&(this.#r=t,this.#C({listeners:(()=>{if(!e)return!0;let{notifyOnChangeProps:t}=this.options,n=typeof t==`function`?t():t;if(n===`all`||!n&&!this.#m.size)return!0;let r=new Set(n??this.#m);return this.options.throwOnError&&r.add(`error`),Object.keys(this.#r).some(t=>{let n=t;return this.#r[n]!==e[n]&&r.has(n)})})()}))}#S(){let e=this.#e.getQueryCache().build(this.#e,this.options);if(e===this.#t)return;let t=this.#t;this.#t=e,this.#n=e.state,this.hasListeners()&&(t?.removeObserver(this),e.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#y()}#C(e){z.batch(()=>{e.listeners&&this.listeners.forEach(e=>{e(this.#r)}),this.#e.getQueryCache().notify({query:this.#t,type:`observerResultsUpdated`})})}};function De(e,t){return A(t.enabled,e)!==!1&&e.state.data===void 0&&!(e.state.status===`error`&&t.retryOnMount===!1)}function Oe(e,t){return De(e,t)||e.state.data!==void 0&&ke(e,t,t.refetchOnMount)}function ke(e,t,n){if(A(t.enabled,e)!==!1&&k(t.staleTime,e)!==`static`){let r=typeof n==`function`?n(e):n;return r===`always`||r!==!1&&je(e,t)}return!1}function Ae(e,t,n,r){return(e!==t||A(r.enabled,e)===!1)&&(!n.suspense||e.state.status!==`error`)&&je(e,n)}function je(e,t){return A(t.enabled,e)!==!1&&e.isStaleByTime(k(t.staleTime,e))}function Me(e,t){return!F(e.getCurrentResult(),t)}function Ne(e){return{onFetch:(t,n)=>{let r=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,a=t.state.data?.pages||[],o=t.state.data?.pageParams||[],s={pages:[],pageParams:[]},c=0,l=async()=>{let n=!1,l=e=>{de(e,()=>t.signal,()=>n=!0)},u=le(t.options,t.fetchOptions),d=async(e,r,i)=>{if(n)return Promise.reject();if(r==null&&e.pages.length)return Promise.resolve(e);let a=await u((()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:r,direction:i?`backward`:`forward`,meta:t.options.meta};return l(e),e})()),{maxPages:o}=t.options,s=i?se:R;return{pages:s(e.pages,a,o),pageParams:s(e.pageParams,r,o)}};if(i&&a.length){let e=i===`backward`,t=e?Fe:Pe,n={pages:a,pageParams:o};s=await d(n,t(r,n),e)}else{let t=e??a.length;do{let e=c===0?o[0]??r.initialPageParam:Pe(r,s);if(c>0&&e==null)break;s=await d(s,e),c++}while(ct.options.persister?.(l,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},n):t.fetchFn=l}}}function Pe(e,{pages:t,pageParams:n}){let r=t.length-1;return t.length>0?e.getNextPageParam(t[r],t,n[r],n):void 0}function Fe(e,{pages:t,pageParams:n}){return t.length>0?e.getPreviousPageParam?.(t[0],t,n[0],n):void 0}var Ie=class extends xe{#e;#t;#n;#r;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#n=e.mutationCache,this.#t=[],this.state=e.state||Le(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#n.notify({type:`observerAdded`,mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#n.notify({type:`observerRemoved`,mutation:this,observer:e})}optionalRemove(){this.#t.length||(this.state.status===`pending`?this.scheduleGc():this.#n.remove(this))}continue(){return this.#r?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#i({type:`continue`})},n={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#r=be({fn:()=>this.options.mutationFn?this.options.mutationFn(e,n):Promise.reject(Error(`No mutationFn found`)),onFail:(e,t)=>{this.#i({type:`failed`,failureCount:e,error:t})},onPause:()=>{this.#i({type:`pause`})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#n.canRun(this)});let r=this.state.status===`pending`,i=!this.#r.canStart();try{if(r)t();else{this.#i({type:`pending`,variables:e,isPaused:i}),this.#n.config.onMutate&&await this.#n.config.onMutate(e,this,n);let t=await this.options.onMutate?.(e,n);t!==this.state.context&&this.#i({type:`pending`,context:t,variables:e,isPaused:i})}let a=await this.#r.start();return await this.#n.config.onSuccess?.(a,e,this.state.context,this,n),await this.options.onSuccess?.(a,e,this.state.context,n),await this.#n.config.onSettled?.(a,null,this.state.variables,this.state.context,this,n),await this.options.onSettled?.(a,null,e,this.state.context,n),this.#i({type:`success`,data:a}),a}catch(t){try{await this.#n.config.onError?.(t,e,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onError?.(t,e,this.state.context,n)}catch(e){Promise.reject(e)}try{await this.#n.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,n)}catch(e){Promise.reject(e)}try{await this.options.onSettled?.(void 0,t,e,this.state.context,n)}catch(e){Promise.reject(e)}throw this.#i({type:`error`,error:t}),t}finally{this.#n.runNext(this)}}#i(e){let t=t=>{switch(e.type){case`failed`:return{...t,failureCount:e.failureCount,failureReason:e.error};case`pause`:return{...t,isPaused:!0};case`continue`:return{...t,isPaused:!1};case`pending`:return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:`pending`,variables:e.variables,submittedAt:Date.now()};case`success`:return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:`success`,isPaused:!1};case`error`:return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:`error`}}};this.state=t(this.state),z.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#n.notify({mutation:this,type:`updated`,action:e})})}};function Le(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:`idle`,variables:void 0,submittedAt:0}}var Re=class extends b{constructor(e={}){super(),this.config=e,this.#e=new Set,this.#t=new Map,this.#n=0}#e;#t;#n;build(e,t,n){let r=new Ie({client:e,mutationCache:this,mutationId:++this.#n,options:e.defaultMutationOptions(t),state:n});return this.add(r),r}add(e){this.#e.add(e);let t=ze(e);if(typeof t==`string`){let n=this.#t.get(t);n?n.push(e):this.#t.set(t,[e])}this.notify({type:`added`,mutation:e})}remove(e){if(this.#e.delete(e)){let t=ze(e);if(typeof t==`string`){let n=this.#t.get(t);if(n)if(n.length>1){let t=n.indexOf(e);t!==-1&&n.splice(t,1)}else n[0]===e&&this.#t.delete(t)}}this.notify({type:`removed`,mutation:e})}canRun(e){let t=ze(e);if(typeof t==`string`){let n=this.#t.get(t)?.find(e=>e.state.status===`pending`);return!n||n===e}else return!0}runNext(e){let t=ze(e);return typeof t==`string`?(this.#t.get(t)?.find(t=>t!==e&&t.state.isPaused))?.continue()??Promise.resolve():Promise.resolve()}clear(){z.batch(()=>{this.#e.forEach(e=>{this.notify({type:`removed`,mutation:e})}),this.#e.clear(),this.#t.clear()})}getAll(){return Array.from(this.#e)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>ne(t,e))}findAll(e={}){return this.getAll().filter(t=>ne(e,t))}notify(e){z.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return z.batch(()=>Promise.all(e.map(e=>e.continue().catch(E))))}};function ze(e){return e.options.scope?.id}var Be=class extends b{#e;#t=void 0;#n;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#i()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),F(this.options,t)||this.#e.getMutationCache().notify({type:`observerOptionsUpdated`,mutation:this.#n,observer:this}),t?.mutationKey&&this.options.mutationKey&&M(t.mutationKey)!==M(this.options.mutationKey)?this.reset():this.#n?.state.status===`pending`&&this.#n.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#n?.removeObserver(this)}onMutationUpdate(e){this.#i(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#n?.removeObserver(this),this.#n=void 0,this.#i(),this.#a()}mutate(e,t){return this.#r=t,this.#n?.removeObserver(this),this.#n=this.#e.getMutationCache().build(this.#e,this.options),this.#n.addObserver(this),this.#n.execute(e)}#i(){let e=this.#n?.state??Le();this.#t={...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset}}#a(e){z.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,n=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{this.#r.onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{this.#r.onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},Ve=class extends b{constructor(e={}){super(),this.config=e,this.#e=new Map}#e;build(e,t,n){let r=t.queryKey,i=t.queryHash??j(r,t),a=this.get(i);return a||(a=new Se({client:e,queryKey:r,queryHash:i,options:e.defaultQueryOptions(t),state:n,defaultOptions:e.getQueryDefaults(r)}),this.add(a)),a}add(e){this.#e.has(e.queryHash)||(this.#e.set(e.queryHash,e),this.notify({type:`added`,query:e}))}remove(e){let t=this.#e.get(e.queryHash);t&&(e.destroy(),t===e&&this.#e.delete(e.queryHash),this.notify({type:`removed`,query:e}))}clear(){z.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#e.get(e)}getAll(){return[...this.#e.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>te(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>te(e,t)):t}notify(e){z.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){z.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){z.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},He=class{#e;#t;#n;#r;#i;#a;#o;#s;constructor(e={}){this.#e=e.queryCache||new Ve,this.#t=e.mutationCache||new Re,this.#n=e.defaultOptions||{},this.#r=new Map,this.#i=new Map,this.#a=0}mount(){this.#a++,this.#a===1&&(this.#o=x.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onFocus())}),this.#s=ge.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#e.onOnline())}))}unmount(){this.#a--,this.#a===0&&(this.#o?.(),this.#o=void 0,this.#s?.(),this.#s=void 0)}isFetching(e){return this.#e.findAll({...e,fetchStatus:`fetching`}).length}isMutating(e){return this.#t.findAll({...e,status:`pending`}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),n=this.#e.build(this,t),r=n.state.data;return r===void 0?this.fetchQuery(e):(e.revalidateIfStale&&n.isStaleByTime(k(t.staleTime,n))&&this.prefetchQuery(t),Promise.resolve(r))}getQueriesData(e){return this.#e.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,n){let r=this.defaultQueryOptions({queryKey:e}),i=this.#e.get(r.queryHash)?.state.data,a=ee(t,i);if(a!==void 0)return this.#e.build(this,r).setData(a,{...n,manual:!0})}setQueriesData(e,t,n){return z.batch(()=>this.#e.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,n)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#e.get(t.queryHash)?.state}removeQueries(e){let t=this.#e;z.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let n=this.#e;return z.batch(()=>(n.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:`active`,...e},t)))}cancelQueries(e,t={}){let n={revert:!0,...t},r=z.batch(()=>this.#e.findAll(e).map(e=>e.cancel(n)));return Promise.all(r).then(E).catch(E)}invalidateQueries(e,t={}){return z.batch(()=>(this.#e.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType===`none`?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??`active`},t)))}refetchQueries(e,t={}){let n={...t,cancelRefetch:t.cancelRefetch??!0},r=z.batch(()=>this.#e.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,n);return n.throwOnError||(t=t.catch(E)),e.state.fetchStatus===`paused`?Promise.resolve():t}));return Promise.all(r).then(E)}fetchQuery(e){let t=this.defaultQueryOptions(e);t.retry===void 0&&(t.retry=!1);let n=this.#e.build(this,t);return n.isStaleByTime(k(t.staleTime,n))?n.fetch(t):Promise.resolve(n.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(E).catch(E)}fetchInfiniteQuery(e){return e.behavior=Ne(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(E).catch(E)}ensureInfiniteQueryData(e){return e.behavior=Ne(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return ge.isOnline()?this.#t.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#e}getMutationCache(){return this.#t}getDefaultOptions(){return this.#n}setDefaultOptions(e){this.#n=e}setQueryDefaults(e,t){this.#r.set(M(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#r.values()],n={};return t.forEach(t=>{N(e,t.queryKey)&&Object.assign(n,t.defaultOptions)}),n}setMutationDefaults(e,t){this.#i.set(M(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#i.values()],n={};return t.forEach(t=>{N(e,t.mutationKey)&&Object.assign(n,t.defaultOptions)}),n}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#n.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||=j(t.queryKey,t),t.refetchOnReconnect===void 0&&(t.refetchOnReconnect=t.networkMode!==`always`),t.throwOnError===void 0&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode=`offlineFirst`),t.queryFn===ce&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#n.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#e.clear(),this.#t.clear()}},Ue=f.createContext(void 0),We=e=>{let t=f.useContext(Ue);if(e)return e;if(!t)throw Error(`No QueryClient set, use QueryClientProvider to set one`);return t},Ge=({client:e,children:t})=>(f.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,v.jsx)(Ue.Provider,{value:e,children:t})),Ke=f.createContext(!1),qe=()=>f.useContext(Ke);Ke.Provider;function Je(){let e=!1;return{clearReset:()=>{e=!1},reset:()=>{e=!0},isReset:()=>e}}var Ye=f.createContext(Je()),Xe=()=>f.useContext(Ye),Ze=(e,t,n)=>{let r=n?.state.error&&typeof e.throwOnError==`function`?ue(e.throwOnError,[n.state.error,n]):e.throwOnError;(e.suspense||e.experimental_prefetchInRender||r)&&(t.isReset()||(e.retryOnMount=!1))},Qe=e=>{f.useEffect(()=>{e.clearReset()},[e])},$e=({result:e,errorResetBoundary:t,throwOnError:n,query:r,suspense:i})=>e.isError&&!t.isReset()&&!e.isFetching&&r&&(i&&e.data===void 0||ue(n,[e.error,r])),et=e=>{if(e.suspense){let t=1e3,n=e=>e===`static`?e:Math.max(e??t,t),r=e.staleTime;e.staleTime=typeof r==`function`?(...e)=>n(r(...e)):n(r),typeof e.gcTime==`number`&&(e.gcTime=Math.max(e.gcTime,t))}},tt=(e,t)=>e.isLoading&&e.isFetching&&!t,nt=(e,t)=>e?.suspense&&t.isPending,rt=(e,t,n)=>t.fetchOptimistic(e).catch(()=>{n.clearReset()});function it(e,t,n){let r=qe(),i=Xe(),a=We(n),o=a.defaultQueryOptions(e);a.getDefaultOptions().queries?._experimental_beforeQuery?.(o);let s=a.getQueryCache().get(o.queryHash);o._optimisticResults=r?`isRestoring`:`optimistic`,et(o),Ze(o,i,s),Qe(i);let c=!a.getQueryCache().get(o.queryHash),[l]=f.useState(()=>new t(a,o)),u=l.getOptimisticResult(o),d=!r&&e.subscribed!==!1;if(f.useSyncExternalStore(f.useCallback(e=>{let t=d?l.subscribe(z.batchCalls(e)):E;return l.updateResult(),t},[l,d]),()=>l.getCurrentResult(),()=>l.getCurrentResult()),f.useEffect(()=>{l.setOptions(o)},[o,l]),nt(o,u))throw rt(o,l,i);if($e({result:u,errorResetBoundary:i,throwOnError:o.throwOnError,query:s,suspense:o.suspense}))throw u.error;return a.getDefaultOptions().queries?._experimental_afterQuery?.(o,u),o.experimental_prefetchInRender&&!fe.isServer()&&tt(u,r)&&(c?rt(o,l,i):s?.promise)?.catch(E).finally(()=>{l.updateResult()}),o.notifyOnChangeProps?u:l.trackResult(u)}function at(e,t){return it(e,Ee,t)}function ot(e,t){let n=We(t),[r]=f.useState(()=>new Be(n,e));f.useEffect(()=>{r.setOptions(e)},[r,e]);let i=f.useSyncExternalStore(f.useCallback(e=>r.subscribe(z.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),a=f.useCallback((e,t)=>{r.mutate(e,t).catch(E)},[r]);if(i.error&&ue(r.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:a,mutateAsync:i.mutate}}function st({children:t}){let[n]=(0,e.useState)(()=>new He({defaultOptions:{queries:{staleTime:3e4,gcTime:5*6e4,refetchOnWindowFocus:!1,retry:!1}}}));return(0,v.jsx)(Ge,{client:n,children:t})}function ct(){if(window.Injector?.default===void 0)throw TypeError(`SilverStripe Injector is not available. Ensure the admin bundle is loaded before the grid editor.`);return window.Injector.default}function lt(e,t){if(typeof window.Injector?.loadComponent!=`function`)throw TypeError(`SilverStripe Injector is not available. Ensure the admin bundle is loaded before the grid editor.`);return window.Injector.loadComponent(e,t)}var ut=`.grid-editor__container`,dt=`data-grid-editor-mounted`,ft=new WeakMap;function pt(e){let t=typeof e==`object`&&e?e:{},n=t[`grid-page-id`],r=t[`grid-zone`],i=t[`grid-readonly`],a=t[`grid-version`];return{pageId:typeof n==`number`?n:null,zone:typeof r==`string`&&r!==``?r:`main`,readonly:i===!0,version:typeof a==`number`?a:void 0}}function mt(e){let t=e.getAttribute(`data-schema`);if(t===null||t===``)return null;try{return JSON.parse(t)}catch{return null}}function ht(n,r){if(!ft.has(n))try{let i=lt(`GridEditor`),{pageId:a,zone:o,readonly:s,version:c}=pt(r),l=(0,t.createRoot)(n);ft.set(n,l),n.setAttribute(dt,`true`),l.render((0,e.createElement)(st,null,(0,e.createElement)(y,null,(0,e.createElement)(i,{pageId:a,zone:o,readonly:s,version:c}))))}catch(e){console.warn(`[GridEditor] Failed to mount grid editor.`,e)}}function gt(e){let t=ft.get(e);if(t!==void 0){ft.delete(e),e.removeAttribute(dt);try{t.unmount()}catch(e){console.warn(`[GridEditor] Error during unmount (element already detached).`,e)}}}function _t(){typeof window>`u`||window.jQuery?.entwine===void 0||window.jQuery.entwine(`ss`,e=>{e(`.js-injector-boot ${ut}`).entwine({onmatch(){let e=this[0];ht(e,this.data(`schema`));let t=ft.get(e)??null;this.setReactRoot(t)},onunmatch(){let e=this[0];gt(e),this.setReactRoot(null)}})})}function vt(){if(typeof document>`u`||typeof MutationObserver>`u`)return;let e=e=>{if(e instanceof HTMLElement){e.matches(ut)&&ht(e,mt(e));for(let t of e.querySelectorAll(ut))ht(t,mt(t))}},t=e=>{if(e instanceof HTMLElement){e.matches(ut)&>(e);for(let t of e.querySelectorAll(ut))gt(t)}};new MutationObserver(n=>{for(let r of n)r.addedNodes.forEach(e),r.removedNodes.forEach(t)}).observe(document.body,{childList:!0,subtree:!0});for(let e of document.querySelectorAll(ut))ht(e,mt(e))}_t(),vt(),typeof window<`u`&&window.jQuery?.entwine!==void 0&&window.jQuery.entwine(`ss`,e=>{e(`.grid-settings-field .grid-settings-field__override-toggle`).entwine({onchange(){let t=e(this).closest(`tr`),n=e(this).is(`:checked`);t.find(`select, input`).not(`.grid-settings-field__override-toggle`).prop(`disabled`,!n),t.toggleClass(`is-overridden`,n)}})});function yt(){for(var t=arguments.length,n=Array(t),r=0;re=>{n.forEach(t=>t(e))},n)}var bt=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function xt(e){let t=Object.prototype.toString.call(e);return t===`[object Window]`||t===`[object global]`}function St(e){return`nodeType`in e}function B(e){return e?xt(e)?e:St(e)?e.ownerDocument?.defaultView??window:window:window}function Ct(e){let{Document:t}=B(e);return e instanceof t}function wt(e){return xt(e)?!1:e instanceof B(e).HTMLElement}function Tt(e){return e instanceof B(e).SVGElement}function Et(e){return e?xt(e)?e.document:St(e)?Ct(e)?e:wt(e)||Tt(e)?e.ownerDocument:document:document:document}var Dt=bt?e.useLayoutEffect:e.useEffect;function Ot(t){let n=(0,e.useRef)(t);return Dt(()=>{n.current=t}),(0,e.useCallback)(function(){for(var e=arguments.length,t=Array(e),r=0;r{t.current=setInterval(e,n)},[]),(0,e.useCallback)(()=>{t.current!==null&&(clearInterval(t.current),t.current=null)},[])]}function At(t,n){n===void 0&&(n=[t]);let r=(0,e.useRef)(t);return Dt(()=>{r.current!==t&&(r.current=t)},n),r}function jt(t,n){let r=(0,e.useRef)();return(0,e.useMemo)(()=>{let e=t(r.current);return r.current=e,e},[...n])}function Mt(t){let n=Ot(t),r=(0,e.useRef)(null);return[r,(0,e.useCallback)(e=>{e!==r.current&&n?.(e,r.current),r.current=e},[])]}function Nt(t){let n=(0,e.useRef)();return(0,e.useEffect)(()=>{n.current=t},[t]),n.current}var Pt={};function Ft(t,n){return(0,e.useMemo)(()=>{if(n)return n;let e=Pt[t]==null?0:Pt[t]+1;return Pt[t]=e,t+`-`+e},[t,n])}function It(e){return function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i{let r=Object.entries(n);for(let[n,i]of r){let r=t[n];r!=null&&(t[n]=r+e*i)}return t},{...t})}}var Lt=It(1),Rt=It(-1);function zt(e){return`clientX`in e&&`clientY`in e}function Bt(e){if(!e)return!1;let{KeyboardEvent:t}=B(e.target);return t&&e instanceof t}function Vt(e){if(!e)return!1;let{TouchEvent:t}=B(e.target);return t&&e instanceof t}function Ht(e){if(Vt(e)){if(e.touches&&e.touches.length){let{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){let{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return zt(e)?{x:e.clientX,y:e.clientY}:null}var Ut=Object.freeze({Translate:{toString(e){if(!e)return;let{x:t,y:n}=e;return`translate3d(`+(t?Math.round(t):0)+`px, `+(n?Math.round(n):0)+`px, 0)`}},Scale:{toString(e){if(!e)return;let{scaleX:t,scaleY:n}=e;return`scaleX(`+t+`) scaleY(`+n+`)`}},Transform:{toString(e){if(e)return[Ut.Translate.toString(e),Ut.Scale.toString(e)].join(` `)}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+` `+n+`ms `+r}}}),Wt=`a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]`;function Gt(e){return e.matches(Wt)?e:e.querySelector(Wt)}var Kt={display:`none`};function qt(t){let{id:n,value:r}=t;return e.default.createElement(`div`,{id:n,style:Kt},r)}function Jt(t){let{id:n,announcement:r,ariaLiveType:i=`assertive`}=t;return e.default.createElement(`div`,{id:n,style:{position:`fixed`,top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:`hidden`,clip:`rect(0 0 0 0)`,clipPath:`inset(100%)`,whiteSpace:`nowrap`},role:`status`,"aria-live":i,"aria-atomic":!0},r)}function Yt(){let[t,n]=(0,e.useState)(``);return{announce:(0,e.useCallback)(e=>{e!=null&&n(e)},[]),announcement:t}}var Xt=(0,e.createContext)(null);function Zt(t){let n=(0,e.useContext)(Xt);(0,e.useEffect)(()=>{if(!n)throw Error(`useDndMonitor must be used within a children of `);return n(t)},[t,n])}function Qt(){let[t]=(0,e.useState)(()=>new Set),n=(0,e.useCallback)(e=>(t.add(e),()=>t.delete(e)),[t]);return[(0,e.useCallback)(e=>{let{type:n,event:r}=e;t.forEach(e=>e[n]?.call(e,r))},[t]),n]}var $t={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},Jt={onDragStart(e){let{active:t}=e;return`Picked up draggable item `+t.id+`.`},onDragOver(e){let{active:t,over:n}=e;return n?`Draggable item `+t.id+` was moved over droppable area `+n.id+`.`:`Draggable item `+t.id+` is no longer over a droppable area.`},onDragEnd(e){let{active:t,over:n}=e;return n?`Draggable item `+t.id+` was dropped over droppable area `+n.id:`Draggable item `+t.id+` was dropped.`},onDragCancel(e){let{active:t}=e;return`Dragging was cancelled. Draggable item `+t.id+` was dropped.`}};function Yt(t){let{announcements:r=Jt,container:i,hiddenTextDescribedById:a,screenReaderInstructions:o=qt}=t,{announce:s,announcement:c}=Ut(),l=kt(`DndLiveRegion`),[u,d]=(0,e.useState)(!1);if((0,e.useEffect)(()=>{d(!0)},[]),Gt((0,e.useMemo)(()=>({onDragStart(e){let{active:t}=e;s(r.onDragStart({active:t}))},onDragMove(e){let{active:t,over:n}=e;r.onDragMove&&s(r.onDragMove({active:t,over:n}))},onDragOver(e){let{active:t,over:n}=e;s(r.onDragOver({active:t,over:n}))},onDragEnd(e){let{active:t,over:n}=e;s(r.onDragEnd({active:t,over:n}))},onDragCancel(e){let{active:t,over:n}=e;s(r.onDragCancel({active:t,over:n}))}}),[s,r])),!u)return null;let f=e.default.createElement(e.default.Fragment,null,e.default.createElement(Vt,{id:a,value:o.draggable}),e.default.createElement(Ht,{id:l,announcement:c}));return i?(0,n.createPortal)(f,i):f}var q;(function(e){e.DragStart=`dragStart`,e.DragMove=`dragMove`,e.DragEnd=`dragEnd`,e.DragCancel=`dragCancel`,e.DragOver=`dragOver`,e.RegisterDroppable=`registerDroppable`,e.SetDroppableDisabled=`setDroppableDisabled`,e.UnregisterDroppable=`unregisterDroppable`})(q||={});function Xt(){}function Zt(t,n){return(0,e.useMemo)(()=>({sensor:t,options:n??{}}),[t,n])}function Qt(){for(var t=arguments.length,n=Array(t),r=0;r[...n].filter(e=>e!=null),[...n])}var J=Object.freeze({x:0,y:0});function $t(e,t){return Math.sqrt((e.x-t.x)**2+(e.y-t.y)**2)}function en(e,t){let n=It(e);if(!n)return`0 0`;let r={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return r.x+`% `+r.y+`%`}function tn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function nn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function rn(e,t){if(!e||e.length===0)return null;let[n]=e;return t?n[t]:n}function an(e,t,n){return t===void 0&&(t=e.left),n===void 0&&(n=e.top),{x:t+e.width*.5,y:n+e.height*.5}}var on=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e,i=an(t,t.left,t.top),a=[];for(let e of r){let{id:t}=e,r=n.get(t);if(r){let n=$t(an(r),i);a.push({id:t,data:{droppableContainer:e,value:n}})}}return a.sort(tn)};function sn(e,t){let n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),i=Math.min(t.left+t.width,e.left+e.width),a=Math.min(t.top+t.height,e.top+e.height),o=i-r,s=a-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e,i=[];for(let e of r){let{id:r}=e,a=n.get(r);if(a){let n=sn(a,t);n>0&&i.push({id:r,data:{droppableContainer:e,value:n}})}}return i.sort(nn)};function ln(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function un(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:J}function dn(e){return function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x}),{...t})}}var fn=dn(1);function pn(e){if(e.startsWith(`matrix3d(`)){let t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith(`matrix(`)){let t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function mn(e,t,n){let r=pn(t);if(!r)return e;let{scaleX:i,scaleY:a,x:o,y:s}=r,c=e.left-o-(1-i)*parseFloat(n),l=e.top-s-(1-a)*parseFloat(n.slice(n.indexOf(` `)+1)),u=i?e.width/i:e.width,d=a?e.height/a:e.height;return{width:u,height:d,top:l,right:c+u,bottom:l+d,left:c}}var hn={ignoreTransform:!1};function gn(e,t){t===void 0&&(t=hn);let n=e.getBoundingClientRect();if(t.ignoreTransform){let{transform:t,transformOrigin:r}=G(e).getComputedStyle(e);t&&(n=mn(n,t,r))}let{top:r,left:i,width:a,height:o,bottom:s,right:c}=n;return{top:r,left:i,width:a,height:o,bottom:s,right:c}}function _n(e){return gn(e,{ignoreTransform:!0})}function vn(e){let t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function yn(e,t){return t===void 0&&(t=G(e).getComputedStyle(e)),t.position===`fixed`}function bn(e,t){t===void 0&&(t=G(e).getComputedStyle(e));let n=/(auto|scroll|overlay)/;return[`overflow`,`overflowX`,`overflowY`].some(e=>{let r=t[e];return typeof r==`string`?n.test(r):!1})}function xn(e,t){let n=[];function r(i){if(t!=null&&n.length>=t||!i)return n;if(vt(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!yt(i)||bt(i)||n.includes(i))return n;let a=G(e).getComputedStyle(i);return i!==e&&bn(i,a)&&n.push(i),yn(i,a)?n:r(i.parentNode)}return e?r(e):n}function Sn(e){let[t]=xn(e,1);return t??null}function Cn(e){return!ht||!e?null:gt(e)?e:_t(e)?vt(e)||e===xt(e).scrollingElement?window:yt(e)?e:null:null}function wn(e){return gt(e)?e.scrollX:e.scrollLeft}function Tn(e){return gt(e)?e.scrollY:e.scrollTop}function En(e){return{x:wn(e),y:Tn(e)}}var Y;(function(e){e[e.Forward=1]=`Forward`,e[e.Backward=-1]=`Backward`})(Y||={});function Dn(e){return!ht||!e?!1:e===document.scrollingElement}function On(e){let t={x:0,y:0},n=Dn(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=r.y,isRight:e.scrollLeft>=r.x,maxScroll:r,minScroll:t}}var kn={x:.2,y:.2};function An(e,t,n,r,i){let{top:a,left:o,right:s,bottom:c}=n;r===void 0&&(r=10),i===void 0&&(i=kn);let{isTop:l,isBottom:u,isLeft:d,isRight:f}=On(e),p={x:0,y:0},m={x:0,y:0},h={height:t.height*i.y,width:t.width*i.x};return!l&&a<=t.top+h.height?(p.y=Y.Backward,m.y=r*Math.abs((t.top+h.height-a)/h.height)):!u&&c>=t.bottom-h.height&&(p.y=Y.Forward,m.y=r*Math.abs((t.bottom-h.height-c)/h.height)),!f&&s>=t.right-h.width?(p.x=Y.Forward,m.x=r*Math.abs((t.right-h.width-s)/h.width)):!d&&o<=t.left+h.width&&(p.x=Y.Backward,m.x=r*Math.abs((t.left+h.width-o)/h.width)),{direction:p,speed:m}}function jn(e){if(e===document.scrollingElement){let{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}let{top:t,left:n,right:r,bottom:i}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:i,width:e.clientWidth,height:e.clientHeight}}function Mn(e){return e.reduce((e,t)=>jt(e,En(t)),J)}function Nn(e){return e.reduce((e,t)=>e+wn(t),0)}function Pn(e){return e.reduce((e,t)=>e+Tn(t),0)}function Fn(e,t){if(t===void 0&&(t=gn),!e)return;let{top:n,left:r,bottom:i,right:a}=t(e);Sn(e)&&(i<=0||a<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:`center`,inline:`center`})}var In=[[`x`,[`left`,`right`],Nn],[`y`,[`top`,`bottom`],Pn]],Ln=class{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;let n=xn(t),r=Mn(n);this.rect={...e},this.width=e.width,this.height=e.height;for(let[e,t,i]of In)for(let a of t)Object.defineProperty(this,a,{get:()=>{let t=i(n),o=r[e]-t;return this.rect[a]+o},enumerable:!0});Object.defineProperty(this,`rect`,{enumerable:!1})}},Rn=class{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>this.target?.removeEventListener(...e))},this.target=e}add(e,t,n){var r;(r=this.target)==null||r.addEventListener(e,t,n),this.listeners.push([e,t,n])}};function zn(e){let{EventTarget:t}=G(e);return e instanceof t?e:xt(e)}function Bn(e,t){let n=Math.abs(e.x),r=Math.abs(e.y);return typeof t==`number`?Math.sqrt(n**2+r**2)>t:`x`in t&&`y`in t?n>t.x&&r>t.y:`x`in t?n>t.x:`y`in t?r>t.y:!1}var X;(function(e){e.Click=`click`,e.DragStart=`dragstart`,e.Keydown=`keydown`,e.ContextMenu=`contextmenu`,e.Resize=`resize`,e.SelectionChange=`selectionchange`,e.VisibilityChange=`visibilitychange`})(X||={});function Vn(e){e.preventDefault()}function Hn(e){e.stopPropagation()}var Z;(function(e){e.Space=`Space`,e.Down=`ArrowDown`,e.Right=`ArrowRight`,e.Left=`ArrowLeft`,e.Up=`ArrowUp`,e.Esc=`Escape`,e.Enter=`Enter`,e.Tab=`Tab`})(Z||={});var Un={start:[Z.Space,Z.Enter],cancel:[Z.Esc],end:[Z.Space,Z.Enter,Z.Tab]},Wn=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Z.Right:return{...n,x:n.x+25};case Z.Left:return{...n,x:n.x-25};case Z.Down:return{...n,y:n.y+25};case Z.Up:return{...n,y:n.y-25}}},Gn=class{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;let{event:{target:t}}=e;this.props=e,this.listeners=new Rn(xt(t)),this.windowListeners=new Rn(G(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(X.Resize,this.handleCancel),this.windowListeners.add(X.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(X.Keydown,this.handleKeyDown))}handleStart(){let{activeNode:e,onStart:t}=this.props,n=e.node.current;n&&Fn(n),t(J)}handleKeyDown(e){if(Pt(e)){let{active:t,context:n,options:r}=this.props,{keyboardCodes:i=Un,coordinateGetter:a=Wn,scrollBehavior:o=`smooth`}=r,{code:s}=e;if(i.end.includes(s)){this.handleEnd(e);return}if(i.cancel.includes(s)){this.handleCancel(e);return}let{collisionRect:c}=n.current,l=c?{x:c.left,y:c.top}:J;this.referenceCoordinates||=l;let u=a(e,{active:t,context:n.current,currentCoordinates:l});if(u){let t=Mt(u,l),r={x:0,y:0},{scrollableAncestors:i}=n.current;for(let n of i){let i=e.code,{isTop:a,isRight:s,isLeft:c,isBottom:l,maxScroll:d,minScroll:f}=On(n),p=jn(n),m={x:Math.min(i===Z.Right?p.right-p.width/2:p.right,Math.max(i===Z.Right?p.left:p.left+p.width/2,u.x)),y:Math.min(i===Z.Down?p.bottom-p.height/2:p.bottom,Math.max(i===Z.Down?p.top:p.top+p.height/2,u.y))},h=i===Z.Right&&!s||i===Z.Left&&!c,g=i===Z.Down&&!l||i===Z.Up&&!a;if(h&&m.x!==u.x){let e=n.scrollLeft+t.x,a=i===Z.Right&&e<=d.x||i===Z.Left&&e>=f.x;if(a&&!t.y){n.scrollTo({left:e,behavior:o});return}a?r.x=n.scrollLeft-e:r.x=i===Z.Right?n.scrollLeft-d.x:n.scrollLeft-f.x,r.x&&n.scrollBy({left:-r.x,behavior:o});break}else if(g&&m.y!==u.y){let e=n.scrollTop+t.y,a=i===Z.Down&&e<=d.y||i===Z.Up&&e>=f.y;if(a&&!t.x){n.scrollTo({top:e,behavior:o});return}a?r.y=n.scrollTop-e:r.y=i===Z.Down?n.scrollTop-d.y:n.scrollTop-f.y,r.y&&n.scrollBy({top:-r.y,behavior:o});break}}this.handleMove(e,jt(Mt(u,this.referenceCoordinates),r))}}}handleMove(e,t){let{onMove:n}=this.props;e.preventDefault(),n(t)}handleEnd(e){let{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){let{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}};Gn.activators=[{eventName:`onKeyDown`,handler:(e,t,n)=>{let{keyboardCodes:r=Un,onActivation:i}=t,{active:a}=n,{code:o}=e.nativeEvent;if(r.start.includes(o)){let t=a.activatorNode.current;return t&&e.target!==t?!1:(e.preventDefault(),i?.({event:e.nativeEvent}),!0)}return!1}}];function Kn(e){return!!(e&&`distance`in e)}function qn(e){return!!(e&&`delay`in e)}var Jn=class{constructor(e,t,n){n===void 0&&(n=zn(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;let{event:r}=e,{target:i}=r;this.props=e,this.events=t,this.document=xt(i),this.documentListeners=new Rn(this.document),this.listeners=new Rn(n),this.windowListeners=new Rn(G(i)),this.initialCoordinates=It(r)??J,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){let{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:n}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(X.Resize,this.handleCancel),this.windowListeners.add(X.DragStart,Vn),this.windowListeners.add(X.VisibilityChange,this.handleCancel),this.windowListeners.add(X.ContextMenu,Vn),this.documentListeners.add(X.Keydown,this.handleKeydown),t){if(n!=null&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(qn(t)){this.timeoutId=setTimeout(this.handleStart,t.delay),this.handlePending(t);return}if(Kn(t)){this.handlePending(t);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){let{active:n,onPending:r}=this.props;r(n,e,this.initialCoordinates,t)}handleStart(){let{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(X.Click,Hn,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(X.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){let{activated:t,initialCoordinates:n,props:r}=this,{onMove:i,options:{activationConstraint:a}}=r;if(!n)return;let o=It(e)??J,s=Mt(n,o);if(!t&&a){if(Kn(a)){if(a.tolerance!=null&&Bn(s,a.tolerance))return this.handleCancel();if(Bn(s,a.distance))return this.handleStart()}if(qn(a)&&Bn(s,a.tolerance))return this.handleCancel();this.handlePending(a,s);return}e.cancelable&&e.preventDefault(),i(o)}handleEnd(){let{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){let{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===Z.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}},Yn={cancel:{name:`pointercancel`},move:{name:`pointermove`},end:{name:`pointerup`}},Xn=class extends Jn{constructor(e){let{event:t}=e,n=xt(t.target);super(e,Yn,n)}};Xn.activators=[{eventName:`onPointerDown`,handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];var Zn={move:{name:`mousemove`},end:{name:`mouseup`}},Qn;(function(e){e[e.RightClick=2]=`RightClick`})(Qn||={});var $n=class extends Jn{constructor(e){super(e,Zn,xt(e.event.target))}};$n.activators=[{eventName:`onMouseDown`,handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button===Qn.RightClick?!1:(r?.({event:n}),!0)}}];var er={cancel:{name:`touchcancel`},move:{name:`touchmove`},end:{name:`touchend`}},tr=class extends Jn{constructor(e){super(e,er)}static setup(){return window.addEventListener(er.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(er.move.name,e)};function e(){}}};tr.activators=[{eventName:`onTouchStart`,handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t,{touches:i}=n;return i.length>1?!1:(r?.({event:n}),!0)}}];var nr;(function(e){e[e.Pointer=0]=`Pointer`,e[e.DraggableRect=1]=`DraggableRect`})(nr||={});var rr;(function(e){e[e.TreeOrder=0]=`TreeOrder`,e[e.ReversedTreeOrder=1]=`ReversedTreeOrder`})(rr||={});function ir(t){let{acceleration:n,activator:r=nr.Pointer,canScroll:i,draggingRect:a,enabled:o,interval:s=5,order:c=rr.TreeOrder,pointerCoordinates:l,scrollableAncestors:u,scrollableAncestorRects:d,delta:f,threshold:p}=t,m=or({delta:f,disabled:!o}),[h,g]=Ct(),_=(0,e.useRef)({x:0,y:0}),v=(0,e.useRef)({x:0,y:0}),y=(0,e.useMemo)(()=>{switch(r){case nr.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case nr.DraggableRect:return a}},[r,a,l]),b=(0,e.useRef)(null),x=(0,e.useCallback)(()=>{let e=b.current;if(!e)return;let t=_.current.x*v.current.x,n=_.current.y*v.current.y;e.scrollBy(t,n)},[]),S=(0,e.useMemo)(()=>c===rr.TreeOrder?[...u].reverse():u,[c,u]);(0,e.useEffect)(()=>{if(!o||!u.length||!y){g();return}for(let e of S){if(i?.(e)===!1)continue;let t=d[u.indexOf(e)];if(!t)continue;let{direction:r,speed:a}=An(e,t,y,n,p);for(let e of[`x`,`y`])m[e][r[e]]||(a[e]=0,r[e]=0);if(a.x>0||a.y>0){g(),b.current=e,h(x,s),_.current=a,v.current=r;return}}_.current={x:0,y:0},v.current={x:0,y:0},g()},[n,x,i,g,o,s,JSON.stringify(y),JSON.stringify(m),h,u,S,d,JSON.stringify(p)])}var ar={x:{[Y.Backward]:!1,[Y.Forward]:!1},y:{[Y.Backward]:!1,[Y.Forward]:!1}};function or(e){let{delta:t,disabled:n}=e,r=Dt(t);return Tt(e=>{if(n||!r||!e)return ar;let i={x:Math.sign(t.x-r.x),y:Math.sign(t.y-r.y)};return{x:{[Y.Backward]:e.x[Y.Backward]||i.x===-1,[Y.Forward]:e.x[Y.Forward]||i.x===1},y:{[Y.Backward]:e.y[Y.Backward]||i.y===-1,[Y.Forward]:e.y[Y.Forward]||i.y===1}}},[n,t,r])}function sr(e,t){let n=t==null?void 0:e.get(t),r=n?n.node.current:null;return Tt(e=>t==null?null:r??e??null,[r,t])}function cr(t,n){return(0,e.useMemo)(()=>t.reduce((e,t)=>{let{sensor:r}=t,i=r.activators.map(e=>({eventName:e.eventName,handler:n(e.handler,t)}));return[...e,...i]},[]),[t,n])}var lr;(function(e){e[e.Always=0]=`Always`,e[e.BeforeDragging=1]=`BeforeDragging`,e[e.WhileDragging=2]=`WhileDragging`})(lr||={});var ur;(function(e){e.Optimized=`optimized`})(ur||={});var dr=new Map;function fr(t,n){let{dragging:r,dependencies:i,config:a}=n,[o,s]=(0,e.useState)(null),{frequency:c,measure:l,strategy:u}=a,d=(0,e.useRef)(t),f=_(),p=wt(f),m=(0,e.useCallback)(function(e){e===void 0&&(e=[]),!p.current&&s(t=>t===null?e:t.concat(e.filter(e=>!t.includes(e))))},[p]),h=(0,e.useRef)(null),g=Tt(e=>{if(f&&!r)return dr;if(!e||e===dr||d.current!==t||o!=null){let e=new Map;for(let n of t){if(!n)continue;if(o&&o.length>0&&!o.includes(n.id)&&n.rect.current){e.set(n.id,n.rect.current);continue}let t=n.node.current,r=t?new Ln(l(t),t):null;n.rect.current=r,r&&e.set(n.id,r)}return e}return e},[t,o,r,f,l]);return(0,e.useEffect)(()=>{d.current=t},[t]),(0,e.useEffect)(()=>{f||m()},[r,f]),(0,e.useEffect)(()=>{o&&o.length>0&&s(null)},[JSON.stringify(o)]),(0,e.useEffect)(()=>{f||typeof c!=`number`||h.current!==null||(h.current=setTimeout(()=>{m(),h.current=null},c))},[c,f,m,...i]),{droppableRects:g,measureDroppableContainers:m,measuringScheduled:o!=null};function _(){switch(u){case lr.Always:return!1;case lr.BeforeDragging:return r;default:return!r}}}function pr(e,t){return Tt(n=>e?n||(typeof t==`function`?t(e):e):null,[t,e])}function mr(e,t){return pr(e,t)}function hr(t){let{callback:n,disabled:r}=t,i=St(n),a=(0,e.useMemo)(()=>{if(r||typeof window>`u`||window.MutationObserver===void 0)return;let{MutationObserver:e}=window;return new e(i)},[i,r]);return(0,e.useEffect)(()=>()=>a?.disconnect(),[a]),a}function gr(t){let{callback:n,disabled:r}=t,i=St(n),a=(0,e.useMemo)(()=>{if(r||typeof window>`u`||window.ResizeObserver===void 0)return;let{ResizeObserver:e}=window;return new e(i)},[r]);return(0,e.useEffect)(()=>()=>a?.disconnect(),[a]),a}function _r(e){return new Ln(gn(e),e)}function vr(t,n,r){n===void 0&&(n=_r);let[i,a]=(0,e.useState)(null);function o(){a(e=>{if(!t)return null;if(t.isConnected===!1)return e??r??null;let i=n(t);return JSON.stringify(e)===JSON.stringify(i)?e:i})}let s=hr({callback(e){if(t)for(let n of e){let{type:e,target:r}=n;if(e===`childList`&&r instanceof HTMLElement&&r.contains(t)){o();break}}}}),c=gr({callback:o});return K(()=>{o(),t?(c?.observe(t),s?.observe(document.body,{childList:!0,subtree:!0})):(c?.disconnect(),s?.disconnect())},[t]),i}function yr(e){return un(e,pr(e))}var br=[];function xr(t){let n=(0,e.useRef)(t),r=Tt(e=>t?e&&e!==br&&t&&n.current&&t.parentNode===n.current.parentNode?e:xn(t):br,[t]);return(0,e.useEffect)(()=>{n.current=t},[t]),r}function Sr(t){let[n,r]=(0,e.useState)(null),i=(0,e.useRef)(t),a=(0,e.useCallback)(e=>{let t=Cn(e.target);t&&r(e=>e?(e.set(t,En(t)),new Map(e)):null)},[]);return(0,e.useEffect)(()=>{let e=i.current;if(t!==e){n(e);let o=t.map(e=>{let t=Cn(e);return t?(t.addEventListener(`scroll`,a,{passive:!0}),[t,En(t)]):null}).filter(e=>e!=null);r(o.length?new Map(o):null),i.current=t}return()=>{n(t),n(e)};function n(e){e.forEach(e=>{Cn(e)?.removeEventListener(`scroll`,a)})}},[a,t]),(0,e.useMemo)(()=>t.length?n?Array.from(n.values()).reduce((e,t)=>jt(e,t),J):Mn(t):J,[t,n])}function Cr(t,n){n===void 0&&(n=[]);let r=(0,e.useRef)(null);return(0,e.useEffect)(()=>{r.current=null},n),(0,e.useEffect)(()=>{let e=t!==J;e&&!r.current&&(r.current=t),!e&&r.current&&(r.current=null)},[t]),r.current?Mt(t,r.current):J}function wr(t){(0,e.useEffect)(()=>{if(!ht)return;let e=t.map(e=>{let{sensor:t}=e;return t.setup==null?void 0:t.setup()});return()=>{for(let t of e)t?.()}},t.map(e=>{let{sensor:t}=e;return t}))}function Tr(t,n){return(0,e.useMemo)(()=>t.reduce((e,t)=>{let{eventName:r,handler:i}=t;return e[r]=e=>{i(e,n)},e},{}),[t,n])}function Er(t){return(0,e.useMemo)(()=>t?vn(t):null,[t])}var Dr=[];function Or(t,n){n===void 0&&(n=gn);let[r]=t,i=Er(r?G(r):null),[a,o]=(0,e.useState)(Dr);function s(){o(()=>t.length?t.map(e=>Dn(e)?i:new Ln(n(e),e)):Dr)}let c=gr({callback:s});return K(()=>{c?.disconnect(),s(),t.forEach(e=>c?.observe(e))},[t]),a}function kr(e){if(!e)return null;if(e.children.length>1)return e;let t=e.children[0];return yt(t)?t:e}function Ar(t){let{measure:n}=t,[r,i]=(0,e.useState)(null),a=gr({callback:(0,e.useCallback)(e=>{for(let{target:t}of e)if(yt(t)){i(e=>{let r=n(t);return e?{...e,width:r.width,height:r.height}:r});break}},[n])}),[o,s]=Et((0,e.useCallback)(e=>{let t=kr(e);a?.disconnect(),t&&a?.observe(t),i(t?n(t):null)},[n,a]));return(0,e.useMemo)(()=>({nodeRef:o,rect:r,setRef:s}),[r,o,s])}var jr=[{sensor:Xn,options:{}},{sensor:Gn,options:{}}],Mr={current:{}},Nr={draggable:{measure:_n},droppable:{measure:_n,strategy:lr.WhileDragging,frequency:ur.Optimized},dragOverlay:{measure:gn}},Pr=class extends Map{get(e){return e==null?void 0:super.get(e)??void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){return this.get(e)?.node.current??void 0}},Fr={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Pr,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Xt},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Nr,measureDroppableContainers:Xt,windowRect:null,measuringScheduled:!1},Ir={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:``},dispatch:Xt,draggableNodes:new Map,over:null,measureDroppableContainers:Xt},Lr=(0,e.createContext)(Ir),Rr=(0,e.createContext)(Fr);function zr(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Pr}}}function Br(e,t){switch(t.type){case q.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case q.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case q.DragEnd:case q.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case q.RegisterDroppable:{let{element:n}=t,{id:r}=n,i=new Pr(e.droppable.containers);return i.set(r,n),{...e,droppable:{...e.droppable,containers:i}}}case q.SetDroppableDisabled:{let{id:n,key:r,disabled:i}=t,a=e.droppable.containers.get(n);if(!a||r!==a.key)return e;let o=new Pr(e.droppable.containers);return o.set(n,{...a,disabled:i}),{...e,droppable:{...e.droppable,containers:o}}}case q.UnregisterDroppable:{let{id:n,key:r}=t,i=e.droppable.containers.get(n);if(!i||r!==i.key)return e;let a=new Pr(e.droppable.containers);return a.delete(n),{...e,droppable:{...e.droppable,containers:a}}}default:return e}}function Vr(t){let{disabled:n}=t,{active:r,activatorEvent:i,draggableNodes:a}=(0,e.useContext)(Lr),o=Dt(i),s=Dt(r?.id);return(0,e.useEffect)(()=>{if(!n&&!i&&o&&s!=null){if(!Pt(o)||document.activeElement===o.target)return;let e=a.get(s);if(!e)return;let{activatorNode:t,node:n}=e;if(!t.current&&!n.current)return;requestAnimationFrame(()=>{for(let e of[t.current,n.current]){if(!e)continue;let t=zt(e);if(t){t.focus();break}}})}},[i,n,a,s,o]),null}function Hr(e,t){let{transform:n,...r}=t;return e!=null&&e.length?e.reduce((e,t)=>t({transform:e,...r}),n):n}function Ur(t){return(0,e.useMemo)(()=>({draggable:{...Nr.draggable,...t?.draggable},droppable:{...Nr.droppable,...t?.droppable},dragOverlay:{...Nr.dragOverlay,...t?.dragOverlay}}),[t?.draggable,t?.droppable,t?.dragOverlay])}function Wr(t){let{activeNode:n,measure:r,initialRect:i,config:a=!0}=t,o=(0,e.useRef)(!1),{x:s,y:c}=typeof a==`boolean`?{x:a,y:a}:a;K(()=>{if(!s&&!c||!n){o.current=!1;return}if(o.current||!i)return;let e=n?.node.current;if(!e||e.isConnected===!1)return;let t=un(r(e),i);if(s||(t.x=0),c||(t.y=0),o.current=!0,Math.abs(t.x)>0||Math.abs(t.y)>0){let n=Sn(e);n&&n.scrollBy({top:t.y,left:t.x})}},[n,s,c,i,r])}var Gr=(0,e.createContext)({...J,scaleX:1,scaleY:1}),Kr;(function(e){e[e.Uninitialized=0]=`Uninitialized`,e[e.Initializing=1]=`Initializing`,e[e.Initialized=2]=`Initialized`})(Kr||={});var qr=(0,e.memo)(function(t){let{id:r,accessibility:i,autoScroll:a=!0,children:o,sensors:s=jr,collisionDetection:c=cn,measuring:l,modifiers:u,...d}=t,[f,p]=(0,e.useReducer)(Br,void 0,zr),[m,h]=Kt(),[g,_]=(0,e.useState)(Kr.Uninitialized),v=g===Kr.Initialized,{draggable:{active:y,nodes:b,translate:x},droppable:{containers:S}}=f,C=y==null?null:b.get(y),w=(0,e.useRef)({initial:null,translated:null}),T=(0,e.useMemo)(()=>y==null?null:{id:y,data:C?.data??Mr,rect:w},[y,C]),E=(0,e.useRef)(null),[ee,D]=(0,e.useState)(null),[O,k]=(0,e.useState)(null),A=wt(d,Object.values(d)),te=kt(`DndDescribedBy`,r),ne=(0,e.useMemo)(()=>S.getEnabled(),[S]),j=Ur(l),{droppableRects:M,measureDroppableContainers:N,measuringScheduled:re}=fr(ne,{dragging:v,dependencies:[x.x,x.y],config:j.droppable}),P=sr(b,y),F=(0,e.useMemo)(()=>O?It(O):null,[O]),ie=Ee(),I=mr(P,j.draggable.measure);Wr({activeNode:y==null?null:b.get(y),config:ie.layoutShiftCompensation,initialRect:I,measure:j.draggable.measure});let L=vr(P,j.draggable.measure,I),R=vr(P?P.parentElement:null),z=(0,e.useRef)({activatorEvent:null,active:null,activeNode:P,collisionRect:null,collisions:null,droppableRects:M,draggableNodes:b,draggingNode:null,draggingNodeRect:null,droppableContainers:S,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),B=S.getNodeFor(z.current.over?.id),ae=Ar({measure:j.dragOverlay.measure}),V=ae.nodeRef.current??P,H=v?ae.rect??L:null,oe=!!(ae.nodeRef.current&&ae.rect),se=yr(oe?null:L),ce=Er(V?G(V):null),U=xr(v?B??P:null),le=Or(U),ue=Hr(u,{transform:{x:x.x-se.x,y:x.y-se.y,scaleX:1,scaleY:1},activatorEvent:O,active:T,activeNodeRect:L,containerNodeRect:R,draggingNodeRect:H,over:z.current.over,overlayNodeRect:ae.rect,scrollableAncestors:U,scrollableAncestorRects:le,windowRect:ce}),W=F?jt(F,x):null,de=Sr(U),fe=Cr(de),pe=Cr(de,[L]),me=jt(ue,fe),he=H?fn(H,ue):null,ge=T&&he?c({active:T,collisionRect:he,droppableRects:M,droppableContainers:ne,pointerCoordinates:W}):null,_e=rn(ge,`id`),[ve,ye]=(0,e.useState)(null),be=ln(oe?ue:jt(ue,pe),ve?.rect??null,L),xe=(0,e.useRef)(null),Se=(0,e.useCallback)((e,t)=>{let{sensor:r,options:i}=t;if(E.current==null)return;let a=b.get(E.current);if(!a)return;let o=e.nativeEvent;xe.current=new r({active:E.current,activeNode:a,event:o,options:i,context:z,onAbort(e){if(!b.get(e))return;let{onDragAbort:t}=A.current,n={id:e};t?.(n),m({type:`onDragAbort`,event:n})},onPending(e,t,n,r){if(!b.get(e))return;let{onDragPending:i}=A.current,a={id:e,constraint:t,initialCoordinates:n,offset:r};i?.(a),m({type:`onDragPending`,event:a})},onStart(e){let t=E.current;if(t==null)return;let r=b.get(t);if(!r)return;let{onDragStart:i}=A.current,a={activatorEvent:o,active:{id:t,data:r.data,rect:w}};(0,n.unstable_batchedUpdates)(()=>{i?.(a),_(Kr.Initializing),p({type:q.DragStart,initialCoordinates:e,active:t}),m({type:`onDragStart`,event:a}),D(xe.current),k(o)})},onMove(e){p({type:q.DragMove,coordinates:e})},onEnd:s(q.DragEnd),onCancel:s(q.DragCancel)});function s(e){return async function(){let{active:t,collisions:r,over:i,scrollAdjustedTranslate:a}=z.current,s=null;if(t&&a){let{cancelDrop:n}=A.current;s={activatorEvent:o,active:t,collisions:r,delta:a,over:i},e===q.DragEnd&&typeof n==`function`&&await Promise.resolve(n(s))&&(e=q.DragCancel)}E.current=null,(0,n.unstable_batchedUpdates)(()=>{p({type:e}),_(Kr.Uninitialized),ye(null),D(null),k(null),xe.current=null;let t=e===q.DragEnd?`onDragEnd`:`onDragCancel`;if(s){let e=A.current[t];e?.(s),m({type:t,event:s})}})}}},[b]),Ce=cr(s,(0,e.useCallback)((e,t)=>(n,r)=>{let i=n.nativeEvent,a=b.get(r);if(E.current!==null||!a||i.dndKit||i.defaultPrevented)return;let o={active:a};e(n,t.options,o)===!0&&(i.dndKit={capturedBy:t.sensor},E.current=r,Se(n,t))},[b,Se]));wr(s),K(()=>{L&&g===Kr.Initializing&&_(Kr.Initialized)},[L,g]),(0,e.useEffect)(()=>{let{onDragMove:e}=A.current,{active:t,activatorEvent:r,collisions:i,over:a}=z.current;if(!t||!r)return;let o={active:t,activatorEvent:r,collisions:i,delta:{x:me.x,y:me.y},over:a};(0,n.unstable_batchedUpdates)(()=>{e?.(o),m({type:`onDragMove`,event:o})})},[me.x,me.y]),(0,e.useEffect)(()=>{let{active:e,activatorEvent:t,collisions:r,droppableContainers:i,scrollAdjustedTranslate:a}=z.current;if(!e||E.current==null||!t||!a)return;let{onDragOver:o}=A.current,s=i.get(_e),c=s&&s.rect.current?{id:s.id,rect:s.rect.current,data:s.data,disabled:s.disabled}:null,l={active:e,activatorEvent:t,collisions:r,delta:{x:a.x,y:a.y},over:c};(0,n.unstable_batchedUpdates)(()=>{ye(c),o?.(l),m({type:`onDragOver`,event:l})})},[_e]),K(()=>{z.current={activatorEvent:O,active:T,activeNode:P,collisionRect:he,collisions:ge,droppableRects:M,draggableNodes:b,draggingNode:V,draggingNodeRect:H,droppableContainers:S,over:ve,scrollableAncestors:U,scrollAdjustedTranslate:me},w.current={initial:H,translated:he}},[T,P,ge,he,b,V,H,M,S,ve,U,me]),ir({...ie,delta:x,draggingRect:he,pointerCoordinates:W,scrollableAncestors:U,scrollableAncestorRects:le});let we=(0,e.useMemo)(()=>({active:T,activeNode:P,activeNodeRect:L,activatorEvent:O,collisions:ge,containerNodeRect:R,dragOverlay:ae,draggableNodes:b,droppableContainers:S,droppableRects:M,over:ve,measureDroppableContainers:N,scrollableAncestors:U,scrollableAncestorRects:le,measuringConfiguration:j,measuringScheduled:re,windowRect:ce}),[T,P,L,O,ge,R,ae,b,S,M,ve,N,U,le,j,re,ce]),Te=(0,e.useMemo)(()=>({activatorEvent:O,activators:Ce,active:T,activeNodeRect:L,ariaDescribedById:{draggable:te},dispatch:p,draggableNodes:b,over:ve,measureDroppableContainers:N}),[O,Ce,T,L,p,te,b,ve,N]);return e.default.createElement(Wt.Provider,{value:h},e.default.createElement(Lr.Provider,{value:Te},e.default.createElement(Rr.Provider,{value:we},e.default.createElement(Gr.Provider,{value:be},o)),e.default.createElement(Vr,{disabled:i?.restoreFocus===!1})),e.default.createElement(Yt,{...i,hiddenTextDescribedById:te}));function Ee(){let e=ee?.autoScrollEnabled===!1,t=typeof a==`object`?a.enabled===!1:a===!1,n=v&&!e&&!t;return typeof a==`object`?{...a,enabled:n}:{enabled:n}}}),Jr=(0,e.createContext)(null),Yr=`button`,Xr=`Draggable`;function Zr(t){let{id:n,data:r,disabled:i=!1,attributes:a}=t,o=kt(Xr),{activators:s,activatorEvent:c,active:l,activeNodeRect:u,ariaDescribedById:d,draggableNodes:f,over:p}=(0,e.useContext)(Lr),{role:m=Yr,roleDescription:h=`draggable`,tabIndex:g=0}=a??{},_=l?.id===n,v=(0,e.useContext)(_?Gr:Jr),[y,b]=Et(),[x,S]=Et(),C=Tr(s,n),w=wt(r);return K(()=>(f.set(n,{id:n,key:o,node:y,activatorNode:x,data:w}),()=>{let e=f.get(n);e&&e.key===o&&f.delete(n)}),[f,n]),{active:l,activatorEvent:c,activeNodeRect:u,attributes:(0,e.useMemo)(()=>({role:m,tabIndex:g,"aria-disabled":i,"aria-pressed":_&&m===Yr?!0:void 0,"aria-roledescription":h,"aria-describedby":d.draggable}),[i,m,g,_,h,d.draggable]),isDragging:_,listeners:i?void 0:C,node:y,over:p,setNodeRef:b,setActivatorNodeRef:S,transform:v}}function Qr(){return(0,e.useContext)(Rr)}var $r=`Droppable`,ei={timeout:25};function ti(t){let{data:n,disabled:r=!1,id:i,resizeObserverConfig:a}=t,o=kt($r),{active:s,dispatch:c,over:l,measureDroppableContainers:u}=(0,e.useContext)(Lr),d=(0,e.useRef)({disabled:r}),f=(0,e.useRef)(!1),p=(0,e.useRef)(null),m=(0,e.useRef)(null),{disabled:h,updateMeasurementsFor:g,timeout:_}={...ei,...a},v=wt(g??i),y=gr({callback:(0,e.useCallback)(()=>{if(!f.current){f.current=!0;return}m.current!=null&&clearTimeout(m.current),m.current=setTimeout(()=>{u(Array.isArray(v.current)?v.current:[v.current]),m.current=null},_)},[_]),disabled:h||!s}),[b,x]=Et((0,e.useCallback)((e,t)=>{y&&(t&&(y.unobserve(t),f.current=!1),e&&y.observe(e))},[y])),S=wt(n);return(0,e.useEffect)(()=>{!y||!b.current||(y.disconnect(),f.current=!1,y.observe(b.current))},[b,y]),(0,e.useEffect)(()=>(c({type:q.RegisterDroppable,element:{id:i,key:o,disabled:r,node:b,rect:p,data:S}}),()=>c({type:q.UnregisterDroppable,key:o,id:i})),[i]),(0,e.useEffect)(()=>{r!==d.current.disabled&&(c({type:q.SetDroppableDisabled,id:i,key:o,disabled:r}),d.current.disabled=r)},[i,o,r,c]),{active:s,rect:p,isOver:l?.id===i,node:b,over:l,setNodeRef:x}}function ni(t){let{animation:n,children:r}=t,[i,a]=(0,e.useState)(null),[o,s]=(0,e.useState)(null),c=Dt(r);return!r&&!i&&c&&a(c),K(()=>{if(!o)return;let e=i?.key,t=i?.props.id;if(e==null||t==null){a(null);return}Promise.resolve(n(t,o)).then(()=>{a(null)})},[n,i,o]),e.default.createElement(e.default.Fragment,null,r,i?(0,e.cloneElement)(i,{ref:s}):null)}var ri={x:0,y:0,scaleX:1,scaleY:1};function ii(t){let{children:n}=t;return e.default.createElement(Lr.Provider,{value:Ir},e.default.createElement(Gr.Provider,{value:ri},n))}var ai={position:`fixed`,touchAction:`none`},oi=e=>Pt(e)?`transform 250ms ease`:void 0,si=(0,e.forwardRef)((t,n)=>{let{as:r,activatorEvent:i,adjustScale:a,children:o,className:s,rect:c,style:l,transform:u,transition:d=oi}=t;if(!c)return null;let f=a?u:{...u,scaleX:1,scaleY:1},p={...ai,width:c.width,height:c.height,top:c.top,left:c.left,transform:Lt.Transform.toString(f),transformOrigin:a&&i?en(i,c):void 0,transition:typeof d==`function`?d(i):d,...l};return e.default.createElement(r,{className:s,style:p,ref:n},o)}),ci={duration:250,easing:`ease`,keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:Lt.Transform.toString(t)},{transform:Lt.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:r}=t,i={},{styles:a,className:o}=e;if(a!=null&&a.active)for(let[e,t]of Object.entries(a.active))t!==void 0&&(i[e]=n.node.style.getPropertyValue(e),n.node.style.setProperty(e,t));if(a!=null&&a.dragOverlay)for(let[e,t]of Object.entries(a.dragOverlay))t!==void 0&&r.node.style.setProperty(e,t);return o!=null&&o.active&&n.node.classList.add(o.active),o!=null&&o.dragOverlay&&r.node.classList.add(o.dragOverlay),function(){for(let[e,t]of Object.entries(i))n.node.style.setProperty(e,t);o!=null&&o.active&&n.node.classList.remove(o.active)}})({styles:{active:{opacity:`0`}}})};function li(e){let{config:t,draggableNodes:n,droppableContainers:r,measuringConfiguration:i}=e;return St((e,a)=>{if(t===null)return;let o=n.get(e);if(!o)return;let s=o.node.current;if(!s)return;let c=kr(a);if(!c)return;let{transform:l}=G(a).getComputedStyle(a),u=pn(l);if(!u)return;let d=typeof t==`function`?t:ui(t);return Fn(s,i.draggable.measure),d({active:{id:e,data:o.data,node:s,rect:i.draggable.measure(s)},draggableNodes:n,dragOverlay:{node:a,rect:i.dragOverlay.measure(c)},droppableContainers:r,measuringConfiguration:i,transform:u})})}function ui(e){let{duration:t,easing:n,sideEffects:r,keyframes:i}={...ci,...e};return e=>{let{active:a,dragOverlay:o,transform:s,...c}=e;if(!t)return;let l={x:o.rect.left-a.rect.left,y:o.rect.top-a.rect.top},u={scaleX:s.scaleX===1?1:a.rect.width*s.scaleX/o.rect.width,scaleY:s.scaleY===1?1:a.rect.height*s.scaleY/o.rect.height},d={x:s.x-l.x,y:s.y-l.y,...u},f=i({...c,active:a,dragOverlay:o,transform:{initial:s,final:d}}),[p]=f,m=f[f.length-1];if(JSON.stringify(p)===JSON.stringify(m))return;let h=r?.({active:a,dragOverlay:o,...c}),g=o.node.animate(f,{duration:t,easing:n,fill:`forwards`});return new Promise(e=>{g.onfinish=()=>{h?.(),e()}})}}var di=0;function fi(t){return(0,e.useMemo)(()=>{if(t!=null)return di++,di},[t])}var pi=e.default.memo(t=>{let{adjustScale:n=!1,children:r,dropAnimation:i,style:a,transition:o,modifiers:s,wrapperElement:c=`div`,className:l,zIndex:u=999}=t,{activatorEvent:d,active:f,activeNodeRect:p,containerNodeRect:m,draggableNodes:h,droppableContainers:g,dragOverlay:_,over:v,measuringConfiguration:y,scrollableAncestors:b,scrollableAncestorRects:x,windowRect:S}=Qr(),C=(0,e.useContext)(Gr),w=fi(f?.id),T=Hr(s,{activatorEvent:d,active:f,activeNodeRect:p,containerNodeRect:m,draggingNodeRect:_.rect,over:v,overlayNodeRect:_.rect,scrollableAncestors:b,scrollableAncestorRects:x,transform:C,windowRect:S}),E=pr(p),ee=li({config:i,draggableNodes:h,droppableContainers:g,measuringConfiguration:y}),D=E?_.setRef:void 0;return e.default.createElement(ii,null,e.default.createElement(ni,{animation:ee},f&&w?e.default.createElement(si,{key:w,id:f.id,ref:D,as:c,activatorEvent:d,adjustScale:n,className:l,transition:o,rect:E,style:{zIndex:u,...a},transform:T},r):null))});function mi(e,t,n){let r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function hi(e,t){return e.reduce((e,n,r)=>{let i=t.get(n);return i&&(e[r]=i),e},Array(e.length))}function gi(e){return e!==null&&e>=0}function _i(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{let{rects:t,activeNodeRect:n,activeIndex:r,overIndex:i,index:a}=e,o=t[r]??n;if(!o)return null;let s=xi(t,a,r);if(a===r){let e=t[i];return e?{x:rr&&a<=i?{x:-o.width-s,y:0,...yi}:a=i?{x:o.width+s,y:0,...yi}:{x:0,y:0,...yi}};function xi(e,t,n){let r=e[t],i=e[t-1],a=e[t+1];return!r||!i&&!a?0:n{let{rects:t,activeIndex:n,overIndex:r,index:i}=e,a=mi(t,r,n),o=t[i],s=a[i];return!s||!o?null:{x:s.left-o.left,y:s.top-o.top,scaleX:s.width/o.width,scaleY:s.height/o.height}},Ci={scaleX:1,scaleY:1},wi=e=>{let{activeIndex:t,activeNodeRect:n,index:r,rects:i,overIndex:a}=e,o=i[t]??n;if(!o)return null;if(r===t){let e=i[a];return e?{x:0,y:tt&&r<=a?{x:0,y:-o.height-s,...Ci}:r=a?{x:0,y:o.height+s,...Ci}:{x:0,y:0,...Ci}};function Ti(e,t,n){let r=e[t],i=e[t-1],a=e[t+1];return r?ni.map(e=>typeof e==`object`&&`id`in e?e.id:e),[i]),h=s!=null,g=s?m.indexOf(s.id):-1,_=u?m.indexOf(u.id):-1,v=(0,e.useRef)(m),y=!_i(m,v.current),b=_!==-1&&g===-1||y,x=vi(o);K(()=>{y&&h&&d(m)},[y,m,h,d]),(0,e.useEffect)(()=>{v.current=m},[m]);let S=(0,e.useMemo)(()=>({activeIndex:g,containerId:f,disabled:x,disableTransforms:b,items:m,overIndex:_,useDragOverlay:p,sortedRects:hi(m,l),strategy:a}),[g,f,x.draggable,x.droppable,b,m,_,l,p,a]);return e.default.createElement(Di.Provider,{value:S},n)}var ki=e=>{let{id:t,items:n,activeIndex:r,overIndex:i}=e;return mi(n,r,i).indexOf(t)},Ai=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:i,items:a,newIndex:o,previousItems:s,previousContainerId:c,transition:l}=e;return!l||!r||s!==a&&i===o?!1:n?!0:o!==i&&t===c},ji={duration:200,easing:`ease`},Mi=`transform`,Ni=Lt.Transition.toString({property:Mi,duration:0,easing:`linear`}),Pi={roleDescription:`sortable`};function Fi(t){let{disabled:n,index:r,node:i,rect:a}=t,[o,s]=(0,e.useState)(null),c=(0,e.useRef)(r);return K(()=>{if(!n&&r!==c.current&&i.current){let e=a.current;if(e){let t=gn(i.current,{ignoreTransform:!0}),n={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(n.x||n.y)&&s(n)}}r!==c.current&&(c.current=r)},[n,r,i,a]),(0,e.useEffect)(()=>{o&&s(null)},[o]),o}function Ii(t){let{animateLayoutChanges:n=Ai,attributes:r,disabled:i,data:a,getNewIndex:o=ki,id:s,strategy:c,resizeObserverConfig:l,transition:u=ji}=t,{items:d,containerId:f,activeIndex:p,disabled:m,disableTransforms:h,sortedRects:g,overIndex:_,useDragOverlay:v,strategy:y}=(0,e.useContext)(Di),b=Li(i,m),x=d.indexOf(s),S=(0,e.useMemo)(()=>({sortable:{containerId:f,index:x,items:d},...a}),[f,a,x,d]),C=(0,e.useMemo)(()=>d.slice(d.indexOf(s)),[d,s]),{rect:w,node:T,isOver:E,setNodeRef:ee}=ti({id:s,data:S,disabled:b.droppable,resizeObserverConfig:{updateMeasurementsFor:C,...l}}),{active:D,activatorEvent:O,activeNodeRect:k,attributes:A,setNodeRef:te,listeners:ne,isDragging:j,over:M,setActivatorNodeRef:N,transform:re}=Zr({id:s,data:S,attributes:{...Pi,...r},disabled:b.draggable}),P=mt(ee,te),F=!!D,ie=F&&!h&&gi(p)&&gi(_),I=!v&&j,L=ie?(I&&ie?re:null)??(c??y)({rects:g,activeNodeRect:k,activeIndex:p,overIndex:_,index:x}):null,R=gi(p)&&gi(_)?o({id:s,items:d,activeIndex:p,overIndex:_}):x,z=D?.id,B=(0,e.useRef)({activeId:z,items:d,newIndex:R,containerId:f}),ae=d!==B.current.items,V=n({active:D,containerId:f,isDragging:j,isSorting:F,id:s,index:x,items:d,newIndex:B.current.newIndex,previousItems:B.current.items,previousContainerId:B.current.containerId,transition:u,wasDragging:B.current.activeId!=null}),H=Fi({disabled:!V,index:x,node:T,rect:w});return(0,e.useEffect)(()=>{F&&B.current.newIndex!==R&&(B.current.newIndex=R),f!==B.current.containerId&&(B.current.containerId=f),d!==B.current.items&&(B.current.items=d)},[F,R,f,d]),(0,e.useEffect)(()=>{if(z===B.current.activeId)return;if(z!=null&&B.current.activeId==null){B.current.activeId=z;return}let e=setTimeout(()=>{B.current.activeId=z},50);return()=>clearTimeout(e)},[z]),{active:D,activeIndex:p,attributes:A,data:S,rect:w,index:x,newIndex:R,items:d,isOver:E,isSorting:F,isDragging:j,listeners:ne,node:T,overIndex:_,over:M,setNodeRef:P,setActivatorNodeRef:N,setDroppableNodeRef:ee,setDraggableNodeRef:te,transform:H??L,transition:oe()};function oe(){if(H||ae&&B.current.newIndex===x)return Ni;if(!(I&&!Pt(O)||!u)&&(F||V))return Lt.Transition.toString({...u,property:Mi})}}function Li(e,t){return typeof e==`boolean`?{draggable:e,droppable:!1}:{draggable:e?.draggable??t.draggable,droppable:e?.droppable??t.droppable}}Z.Down,Z.Right,Z.Up,Z.Left;function Ri(e){return`containerType`in e}function zi(e){return`containerType`in e&&e.containerType===`section`}var Bi=[`page`,`section`,`row`,`column`,`element`],Vi=`-`;function Hi(e){return Bi.includes(e)}function Ui(e,t){return typeof e==`string`?`${e}${Vi}${t}`:`${e.type}${Vi}${e.id}`}function Wi(e){let t=e.indexOf(Vi),n=e.slice(0,t);if(!Hi(n))return null;let r=Number(e.slice(t+1));return!Number.isInteger(r)||r<=0?null:{type:n,id:r}}function Gi(e,t){return e.type===t.type&&e.id===t.id}function Ki(e,t){if(typeof e!=`object`||!e)throw TypeError(`${t}: expected NodeRef object, got ${typeof e}`);let n=e,r=n.type,i=n.id;if(typeof r!=`string`||!Hi(r))throw TypeError(`${t}: invalid NodeRef.type ${JSON.stringify(r)}`);if(typeof i!=`number`||!Number.isInteger(i)||i<=0)throw TypeError(`${t}: invalid NodeRef.id ${JSON.stringify(i)}`);return{type:r,id:i}}var Q={toKey:Ui,fromKey:Wi,equals:Gi,assert:Ki},qi=class extends Error{status;statusText;constructor(e,t){super(`API error ${e}: ${t}`),this.name=`ApiError`,this.status=e,this.statusText=t}},Ji=class extends Error{constructor(e){super(e),this.name=`ConfigError`}},Yi=`WeDevelop\\Grid\\Controllers\\GridController`;function Xi(){let e=window.ss?.config;if(e===void 0)throw new Ji(`SilverStripe config is not available. Ensure the admin bundle is loaded before the grid editor.`);return e}function Zi(){return Xi().SecurityID}function Qi(){let e=Xi().sections.find(e=>e.name===Yi);if(e===void 0)throw new Ji(`Controller section "${Yi}" not found in CMS config. Ensure the grid module is installed.`);return e}function $(){return Qi().controllerLink.replace(/\/+$/,``)}function $i(){let e=Qi().gridAdapter;if(e===void 0)throw new Ji(`Grid adapter configuration is missing. Ensure the grid module is installed and configured.`);return e}async function ea(e){try{let t=await e.json();if(typeof t==`object`&&t){let e=t;if(typeof e.message==`string`&&e.message!==``)return e.message;if(typeof e.errorMessage==`string`&&e.errorMessage!==``)return e.errorMessage}}catch{}return e.statusText}async function ta(e){let t=await fetch(e,{credentials:`same-origin`,headers:{Accept:`application/json`}});if(!t.ok){let e=await ea(t);throw new qi(t.status,e)}return t.json()}async function na(e,t,n){let r=await fetch(t,{method:e,credentials:`same-origin`,headers:{"Content-Type":`application/json`,Accept:`application/json`,"X-SecurityID":Zi()},body:JSON.stringify(n)});if(!r.ok){let e=await ea(r);throw new qi(r.status,e)}}async function ra(e,t){return na(`POST`,e,t)}async function ia(e,t){return na(`PATCH`,e,t)}function aa(e){if(e===void 0)return``;let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&t.append(n,String(r));let n=t.toString();return n===``?``:`?${n}`}async function oa(e,t){let n=`${e}${aa(t)}`,r=await fetch(n,{method:`DELETE`,credentials:`same-origin`,headers:{Accept:`application/json`,"X-SecurityID":Zi()}});if(!r.ok){let e=await ea(r);throw new qi(r.status,e)}}async function sa(e,t,n){let r=$(),i=encodeURIComponent(t);return ca(await ta(n===void 0?`${r}/api/readTree/${e}/${i}`:`${r}/api/readTree/${e}/${i}/version/${n}`))}function ca(e){if(typeof e!=`object`||!e)throw TypeError(`tree response: expected an object`);let t=e,n=Q.assert(t.rootParent,`tree response.rootParent`);if(!Array.isArray(t.nodes))throw TypeError("tree response: expected `nodes` to be an array");let r={},i=t.overrideCounts;if(typeof i==`object`&&i)for(let[e,t]of Object.entries(i))typeof t==`number`&&(r[e]=t);return{rootParent:n,nodes:t.nodes.map(e=>ua(e)),overrideCounts:r}}function la(e){return e}function ua(e){if(typeof e!=`object`||!e)throw TypeError(`tree node: expected an object`);let t=e,n=Q.assert(t.self,`tree node.self`),r=Q.assert(t.parent,`tree node.parent`),i=la({...t,self:n,parent:r,nodeKey:Q.toKey(n.type,n.id),parentKey:Q.toKey(r.type,r.id),id:n.id});return Ri(i)&&Array.isArray(t.children)&&(i.children=t.children.map(e=>ua(e))),i}async function da(e){await ra(`${$()}/api/create`,e)}async function fa(e){await oa(`${$()}/api/delete`,{id:e})}async function pa(e){await ra(`${$()}/api/duplicate`,{id:e})}async function ma(e){await ia(`${$()}/api/reorder`,e)}async function ha(e){await ra(`${$()}/api/createContent`,e)}async function ga(e){await ia(`${$()}/api/updateGridSettings`,e)}async function _a(e){await oa(`${$()}/api/resetGridSettingsOverrides`,{pageId:e.pageId,zone:e.zone,viewport:e.viewport})}async function va(e){await ra(`${$()}/api/duplicateTo`,e)}async function ya(e,t,n){return ta(`${$()}/api/acceptableContainers/${e}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`)}async function ba(e){return ta(`${$()}/api/zones/${e}`)}async function xa(e){return ta(`${$()}/api/pages${e?`?search=${encodeURIComponent(e)}`:``}`)}var Sa={elementTree:{all:()=>[`elementTree`],byPage:(e,t,n)=>n===void 0?[`elementTree`,e,t]:[`elementTree`,e,t,n]},pages:{all:()=>[`pages`],search:e=>[`pages`,e]},zones:{byPage:e=>[`zones`,e]},acceptableContainers:{byTarget:(e,t,n)=>[`acceptableContainers`,e,t,n]}};function Ca(e,t,n){return{queryKey:e===null?[`elementTree`,`disabled`]:Sa.elementTree.byPage(e,t,n),queryFn:()=>{if(e===null)throw Error(`pageId is required — query should be disabled`);return sa(e,t,n)},enabled:e!==null}}function wa(e,t,n){return et({...Ca(e,t,n)})}function Ta(e,t,n){let{data:r}=et({...Ca(e,t,n),select:e=>e.overrideCounts});return r??{}}function Ea(e){return e!==`page`}function Da(e,t){return Q.toKey(e,t)}function Oa(e){let t=Q.fromKey(e);return t===null||!Ea(t.type)?null:{type:t.type,id:t.id,key:e}}function ka(e){return Oa(e)?.type??null}var Aa={section:`page`,row:`section`,column:`row`,element:`column`};function ja(e,t,n){for(let r of e)t.set(r.nodeKey,r),Ri(r)&&r.children&&(n.set(r.nodeKey,r.children),ja(r.children,t,n))}function Ma(e){let t=new Map,n=new Map,r=Q.toKey(e.rootParent);return n.set(r,e.nodes),ja(e.nodes,t,n),{nodeMap:t,childrenByParentKey:n}}function Na(t){return(0,e.useMemo)(()=>Ma(t),[t])}function Pa(e,t,n,r){let i=Ma(e),a=i.nodeMap.get(t);if(!a)return e;let o=a.parentKey,s=i.childrenByParentKey.get(o);if(!s)return e;let c=s.findIndex(e=>e.nodeKey===t);if(c===-1||Fa(o,c,s,n,r))return e;let l={rootParent:e.rootParent,nodes:structuredClone(e.nodes),overrideCounts:{...e.overrideCounts}},u=Ma(l),d=u.childrenByParentKey.get(o);if(!d)return e;let f=d.findIndex(e=>e.nodeKey===t);if(f===-1)return e;let[p]=d.splice(f,1),m=Q.fromKey(n);if(m===null)return e;let h={...p,parent:m,parentKey:n},g=u.childrenByParentKey.get(n);return g?(Ia(g,h,r),l):e}function Fa(e,t,n,r,i){if(e!==r)return!1;if(i===null)return t===0;let a=n.findIndex(e=>e.nodeKey===i);return a===-1?!1:a+1===t}function Ia(e,t,n){if(n===null){e.unshift(t);return}let r=e.findIndex(e=>e.nodeKey===n);if(r===-1){e.push(t);return}e.splice(r+1,0,t)}function La(){let[t,n]=(0,e.useState)(null),r=(0,e.useRef)(null),i=(0,e.useRef)(null),a=(0,e.useRef)(!1),o=(0,e.useRef)(null),s=(0,e.useRef)(null),c=(0,e.useRef)(null);return{pendingTree:t,collisionRefs:(0,e.useMemo)(()=>({hasPendingMoveRef:a,pendingContainerItemsRef:s,sourceContainerItemsRef:c,overRectRef:o}),[]),applyPendingMove:(0,e.useCallback)((e,t,o,c)=>{let l=Pa(c,Q.toKey(e.type,e.id),t,o===null?null:Q.toKey(e.type,o));if(l===c)return null;let u=Ma(l);r.current=l,i.current=u,a.current=!0;let d=u.childrenByParentKey.get(t)??[];return s.current=new Set(d.map(t=>Da(e.type,t.self.id))),n(l),{tree:l,maps:u}},[]),setSourceSiblings:(0,e.useCallback)(e=>{c.current=e},[]),getEffective:(0,e.useCallback)((e,t)=>r.current!==null&&i.current!==null?{tree:r.current,maps:i.current}:{tree:e,maps:t},[]),clear:(0,e.useCallback)(()=>{r.current=null,i.current=null,a.current=!1,o.current=null,s.current=null,c.current=null,n(null)},[])}}function Ra(e,t,n){let r=n===`column`;return(r?e.x:e.y)<(r?t.left+t.width/2:t.top+t.height/2)?`before`:`after`}function za(e){let t=Oa(e.activeId);if(!t||e.sourceParentKey===e.targetParentKey&&e.sourceIndex===e.overIndex)return null;let n=Ba(e),r=n===null?null:e.maps.nodeMap.get(n)??null;return{element:{type:t.type,id:t.id},parent:e.targetParent,after:r?r.self:null}}function Ba(e){for(let t=e.overIndex-1;t>=0;t--){let n=e.containerItems[t];if(n!==e.activeId)return n}return null}function Va(e){let{activeParsed:t,overParsed:n,pointer:r,maps:i,sourceParentKey:a,sourceIndex:o,overRect:s}=e,c=Q.toKey(t.type,t.id);if(n.type===t.type){let e=Q.toKey(n.type,n.id),l=i.nodeMap.get(e);if(!l)return null;let u=l.parent,d=l.parentKey,f=(i.childrenByParentKey.get(d)??[]).map(e=>e.nodeKey),p=f.filter(e=>e!==c),m;if(a===d){let t=f.indexOf(e);m=t===-1?p.length:t,p.splice(m,0,c)}else{let n=p.indexOf(e);n===-1?m=p.length:(m=n,r!==null&&Ra(r,s,t.type)===`after`&&(m+=1));let i=Math.min(m,p.length);p.splice(i,0,c)}return za({activeId:c,targetParent:u,targetParentKey:d,overIndex:p.indexOf(c),containerItems:p,sourceParentKey:a,sourceIndex:o,maps:i})}let l=Q.toKey(n.type,n.id),u=i.nodeMap.get(l);if(!u||!Ri(u))return null;let d=u.self,f=Q.toKey(d),p=(u.children??[]).map(e=>e.nodeKey).filter(e=>e!==c);return p.push(c),za({activeId:c,targetParent:d,targetParentKey:f,overIndex:p.indexOf(c),containerItems:p,sourceParentKey:a,sourceIndex:o,maps:i})}var Ha=e=>{let{collisionRect:t,droppableContainers:n}=e,r=t.left+t.width/2,i=t.top+t.height/2,a=[];for(let e of n){let t=e.node.current;if(!t)continue;let n=t.getBoundingClientRect(),o=n.left+n.width/2,s=n.top+n.height/2,c=r-o,l=i-s;a.push({id:e.id,data:{droppableContainer:e,value:c*c+l*l}})}return a.sort((e,t)=>e.data?.value-t.data?.value)};function Ua(e,t){let n=ka(e);return n===null?[]:t.filter(e=>ka(String(e.id))===n)}function Wa(e,t){let n=ka(e);if(n===null)return[];let r=Aa[n];return t.filter(e=>{let t=ka(String(e.id));return r===`page`?t===null:t===r})}var Ga=e=>{let{active:t,collisionRect:n,droppableContainers:r,droppableRects:i,pointerCoordinates:a}=e,o=t.rect.current.initial;if(o===null)return[];let s=o.left+o.width/2,c=o.top+o.height/2,l=n.left+n.width/2,u=n.top+n.height/2,d=a?.x??l,f=a?.y??u,p=[];for(let e of r){let t=i.get(e.id);if(t===void 0)continue;let r=t.left+t.width/2,a=t.top+t.height/2,o=co&&g<=o||c=o,v=s>m&&h<=m||s=m,y=d>t.left-50&&dt.top-150&&fe.data?.value-t.data?.value)};function Ka(e){let t=!1,n;return r=>{let i=String(r.active.id),a=e.sourceContainerItemsRef?.current;a!==n&&(t=!1,n=a);let o=r.droppableContainers.filter(e=>e.id!==r.active.id),s=t=>{if(e.overRectRef&&t.length>0){let n=t[0].id,i=r.droppableContainers.find(e=>e.id===n);i?.node.current&&(e.overRectRef.current={id:n,nodeRef:i.node})}return t},c=Ua(i,o);if(e.hasPendingMoveRef.current){let t=e.pendingContainerItemsRef?.current,n=t==null?c:c.filter(e=>t.has(e.id)),i=Ha({...r,droppableContainers:n});if(i.length>0)return i}else{let n=e.sourceContainerItemsRef?.current,i=n&&n.size>0?c.filter(e=>n.has(e.id)):c,a=Ga({...r,droppableContainers:i});if(a.length>0)return t=!0,s(a);let{pointerCoordinates:o,droppableRects:l}=r;if(o&&n&&n.size>0&&i.some(e=>{let t=l.get(e.id);return t===void 0?!1:o.x>=t.left&&o.x<=t.left+t.width&&o.y>=t.top&&o.y<=t.top+t.height})){if(t){let e=Ha({...r,droppableContainers:i});if(e.length>0)return s(e)}return[]}}let l=Wa(i,o);if(r.pointerCoordinates){let e=r.pointerCoordinates,t=l.find(t=>{let n=r.droppableRects.get(t.id);return n===void 0?!1:e.x>=n.left&&e.x<=n.left+n.width&&e.y>=n.top&&e.y<=n.top+n.height});if(t)return s([{id:t.id,data:{droppableContainer:t,value:0}}])}return s(on({...r,droppableContainers:l}))}}var qa=(0,e.createContext)({activeType:null});function Ja(){return(0,e.useContext)(qa)}function Ya(e){let t=e.activatorEvent;if(!(t instanceof PointerEvent))return null;let n=e.active.rect.current.initial,r=e.active.rect.current.translated;return!n||!r?null:{x:r.left+(t.clientX-n.left),y:r.top+(t.clientY-n.top)}}var Xa=8;function Za({tree:t,onReorder:n}){let[r,i]=(0,e.useState)(null),a=Na(t),o=La(),[s]=(0,e.useState)(()=>Ka(o.collisionRefs)),c=Qt(Zt(Xn,{activationConstraint:{distance:Xa}})),l=(0,e.useCallback)(e=>{let t=String(e.active.id),n=Oa(t);if(!n)return;let r=a.nodeMap.get(n.key);if(!r)return;let s=(a.childrenByParentKey.get(r.parentKey)??[]).filter(e=>e.nodeKey!==n.key);o.setSourceSiblings(new Set(s.map(e=>Da(n.type,e.self.id)))),i({activeId:t,activeType:n.type,activeNode:r})},[a,o]),u=(0,e.useCallback)(e=>{let{active:n,over:r}=e;if(!r||n.id===r.id)return;let i=Oa(String(n.id)),s=Oa(String(r.id));if(!i||!s)return;let{tree:c,maps:l}=o.getEffective(t,a),u=l.nodeMap.get(i.key);if(!u)return;let d,f;if(s.type===i.type){let t=l.nodeMap.get(s.key);if(!t)return;d=t.parent;let n=Ya(e);if(n!==null&&Ra(n,r.rect,i.type)===`before`){let e=l.childrenByParentKey.get(t.parentKey)??[],n=e.findIndex(e=>e.nodeKey===s.key);f=n>0?e[n-1].self:null}else f=t.self}else{let e=l.nodeMap.get(s.key);if(!e||!Ri(e))return;d=e.self;let t=e.children??[];f=t.length>0?t[t.length-1].self:null}if(Q.equals(u.parent,d))return;let p=Q.toKey(d.type,d.id);o.applyPendingMove(i,p,f?.id??null,c)},[t,a,o]),d=(0,e.useCallback)(e=>{i(null);let{active:r,over:s}=e;if(!s){o.clear();return}if(r.id===s.id){o.clear();return}let c=Oa(String(r.id)),l=Oa(String(s.id));if(!c||!l){o.clear();return}let u=a.nodeMap.get(c.key);if(!u){o.clear();return}let d=u.parentKey,f=a.childrenByParentKey.get(d);if(!f){o.clear();return}let p=f.findIndex(e=>e.nodeKey===c.key),{maps:m}=o.getEffective(t,a),h=o.collisionRefs.overRectRef.current,g=h?.nodeRef.current,_=String(h?.id)===String(s.id)&&g?g.getBoundingClientRect():s.rect,v=Va({activeParsed:c,overParsed:l,pointer:Ya(e),maps:m,sourceParentKey:d,sourceIndex:p,overRect:_});v?n(v.element,v.parent,v.after,o.clear):o.clear()},[t,a,n,o]),f=(0,e.useCallback)(()=>{i(null),o.clear()},[o]);return{dndContextProps:(0,e.useMemo)(()=>({sensors:c,collisionDetection:s,onDragStart:l,onDragOver:u,onDragEnd:d,onDragCancel:f}),[c,s,l,u,d,f]),dragState:r,pendingTree:o.pendingTree}}function Qa(){let e=window.jQuery?.(`.cms-edit-form`);!e||e.length===0||e.trigger(`aftersubmitform`,{xhr:{getResponseHeader:()=>null}})}function $a(e,t){let n=ze();return{onSuccess:()=>{n.invalidateQueries({queryKey:Sa.elementTree.byPage(e,t)}),Qa()},onError:e=>{p(e.message)}}}function eo(e,t){return tt({mutationFn:da,...$a(e,t)})}function to(e,t){return tt({mutationFn:ha,...$a(e,t)})}function no(e,t){return tt({mutationFn:fa,...$a(e,t)})}function ro(e,t){return tt({mutationFn:pa,...$a(e,t)})}function io(e,t){return tt({mutationFn:va,...$a(e,t)})}function ao(e,t){return tt({mutationFn:ga,...$a(e,t)})}function oo(e,t){return tt({mutationFn:_a,...$a(e,t)})}function so(e,t){let n=ze(),r=Sa.elementTree.byPage(e,t);return tt({mutationFn:({params:e})=>ma(e),onMutate:async({params:e,tree:t,clearPendingTree:i})=>{await n.cancelQueries({queryKey:r});let a=n.getQueryData(r),o=Pa(t,Q.toKey(e.element),Q.toKey(e.parent),e.after===null?null:Q.toKey(e.after));return n.setQueryData(r,o),i?.(),a},onError:(e,{clearPendingTree:t},i)=>{t?.(),i!==void 0&&n.setQueryData(r,i),p(e.message)},onSuccess:async()=>{await n.invalidateQueries({queryKey:r}),Qa()}})}function co(e){return`grid:collapsed:${String(e)}`}function lo(e){try{let t=localStorage.getItem(co(e));if(t===null)return new Set;let n=JSON.parse(t);if(!Array.isArray(n))return new Set;let r=n.filter(e=>typeof e==`string`&&Q.fromKey(e)!==null);return new Set(r)}catch{return new Set}}function uo(e,t){try{localStorage.setItem(co(e),JSON.stringify([...t]))}catch{}}function fo(t){let[n,r]=(0,e.useState)(()=>lo(t)),i=(0,e.useCallback)(e=>n.has(e),[n]),a=(0,e.useCallback)(e=>{r(n=>{let r=new Set(n);return r.has(e)?r.delete(e):r.add(e),uo(t,r),r})},[t]);return(0,e.useMemo)(()=>({isCollapsed:i,toggle:a}),[i,a])}var po=(0,e.createContext)(null);function mo(){let t=(0,e.useContext)(po);if(t===null)throw Error(`useCollapse must be used within a `);return t}var ho=null,go=null;function _o(){return ho===null&&(ho=$i()),ho}function vo(){return _o().viewports}function yo(){return _o().defaultViewport}function bo(){return _o().columnCount}function xo(){return _o().offsetStrategy}function So(){if(go===null){let e=_o().columnCount,t=[];for(let n=1;n<=e;n++)t.push({value:n,label:`${n}/${e}`});t.push({value:`hidden`,label:`hidden`}),go=t}return go}function Co(e){let t=_o().columnCount,n=e===void 0?t-1:t-e,r=[];for(let e=0;e<=n;e++)r.push({value:e,label:e===0?`none`:`+${e}`});return r}function wo(e,t){return e.overrides[t]??e.default}var To=(0,e.createContext)(null);function Eo({initialViewport:t,children:n}){let[r,i]=(0,e.useState)(()=>t??yo()),a=(0,e.useMemo)(()=>({activeViewport:r,setActiveViewport:i}),[r]);return(0,v.jsx)(To.Provider,{value:a,children:n})}function Do(){let t=(0,e.useContext)(To);if(t===null)throw Error(`useViewportContext must be used within a ViewportProvider`);return t}var Oo=(0,e.createContext)(null);function ko({value:e,children:t}){return(0,v.jsx)(Oo.Provider,{value:e,children:t})}function Ao(){let t=(0,e.useContext)(Oo);if(t===null)throw Error(`useGridEditorContext must be used within a GridEditorProvider`);return t}var jo=(0,e.createContext)(!1);function Mo({value:e,children:t}){return(0,v.jsx)(jo.Provider,{value:e,children:t})}function No(){return(0,e.useContext)(jo)}function Po(){let{activeViewport:t}=Do(),{pageId:n,zone:r}=Ao(),i=Ta(n,r),{mutate:a}=oo(n,r),[o,s]=(0,e.useState)(!1),c=t===yo(),l=c?i._total??0:i[t]??0,u=vo().find(e=>e.key===t)?.label??t,d=c?h(`WeDevelopGrid.useResetOverridesAction.RESET_ALL_LABEL`,`Reset all`):h(`WeDevelopGrid.useResetOverridesAction.RESET_VIEWPORT_LABEL`,`Reset viewport`),f=c?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_ALL`,`Reset all overrides`):h(`WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_VIEWPORT`,`Reset {viewport} overrides`,{viewport:u}),p=c&&l===1?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_ONE`,`Reset all viewport overrides across {count} column?`,{count:l}):c?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_MANY`,`Reset all viewport overrides across {count} columns?`,{count:l}):l===1?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_ONE`,`Reset overrides for {count} column on {viewport}?`,{count:l,viewport:u}):h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_MANY`,`Reset overrides for {count} columns on {viewport}?`,{count:l,viewport:u}),m=(0,e.useCallback)(()=>{s(!0)},[]),g=(0,e.useCallback)(()=>{s(!1)},[]),_=(0,e.useCallback)(()=>{s(!1),a(c?{pageId:n,zone:r}:{pageId:n,zone:r,viewport:t})},[c,n,r,t,a]);return{showReset:l>0,label:d,affectedCount:l,isDialogOpen:o,dialogTitle:f,dialogMessage:p,onResetClick:m,onConfirm:_,onCancel:g}}function Fo({isOpen:t,title:n,message:r,confirmLabel:i,onConfirm:a,onCancel:o,destructive:s=!1}){let c=(0,e.useRef)(null);(0,e.useEffect)(()=>{let e=c.current;e!==null&&(t&&!e.open?e.showModal():!t&&e.open&&e.close())},[t]);let l=(0,e.useCallback)(()=>{o()},[o]);return(0,v.jsxs)(`dialog`,{ref:c,className:`confirm-dialog`,"data-testid":`confirm-dialog`,onClose:l,onClick:e=>{e.preventDefault(),e.stopPropagation()},children:[(0,v.jsx)(`div`,{className:`confirm-dialog__header`,children:(0,v.jsx)(`h3`,{className:`confirm-dialog__title`,children:n})}),(0,v.jsx)(`div`,{className:`confirm-dialog__body`,children:(0,v.jsx)(`p`,{className:`confirm-dialog__message`,children:r})}),(0,v.jsxs)(`div`,{className:`confirm-dialog__footer`,children:[(0,v.jsx)(`button`,{type:`button`,className:`confirm-dialog__button confirm-dialog__button--cancel`,onClick:l,children:h(`WeDevelopGrid.ConfirmDialog.CANCEL_BUTTON`,`Cancel`)}),(0,v.jsx)(`button`,{type:`button`,className:`confirm-dialog__button confirm-dialog__button--confirm${s?` confirm-dialog__button--destructive`:``}`,onClick:a,children:i})]})]})}function Io(){let e=No(),t=vo(),{activeViewport:n,setActiveViewport:r}=Do(),i=Po();return(0,v.jsxs)(`div`,{className:`viewport-switcher`,"data-testid":`viewport-switcher`,children:[(0,v.jsx)(`div`,{role:`group`,"aria-label":h(`WeDevelopGrid.ViewportSwitcher.GROUP_LABEL`,`Viewport size`),children:t.map(e=>{let t=e.key===n;return(0,v.jsx)(`button`,{type:`button`,className:`viewport-switcher__button${t?` viewport-switcher__button--active`:``}`,"data-testid":`viewport-button`,"aria-pressed":t,"aria-disabled":t||void 0,onClick:()=>{t||r(e.key)},children:e.label},e.key)})}),!e&&i.showReset&&(0,v.jsx)(`button`,{type:`button`,className:`viewport-switcher__reset`,"data-testid":`reset-overrides-button`,onClick:i.onResetClick,children:i.label}),!e&&(0,v.jsx)(Fo,{isOpen:i.isDialogOpen,title:i.dialogTitle,message:i.dialogMessage,confirmLabel:h(`WeDevelopGrid.ViewportSwitcher.RESET_CONFIRM_LABEL`,`Reset`),onConfirm:i.onConfirm,onCancel:i.onCancel,destructive:!0})]})}function Lo(e){return e.removedfromdraft===void 0?e.addedtodraft===void 0?e.modified===void 0?`published`:`modified`:`draft`:`removed`}var Ro=.3;function zo(e,t,n){return{transform:Lt.Transform.toString(e),transition:t??void 0,opacity:n?Ro:void 0}}function Bo(e,t,n){let r=[e,`${e}--${t}`];for(let[t,i]of Object.entries(n))i&&r.push(`${e}--${t}`);return r.join(` `)}function Vo({listeners:e,attributes:t,label:n=h(`WeDevelopGrid.DragHandle.DEFAULT_LABEL`,`Drag to reorder`)}){return(0,v.jsx)(`button`,{type:`button`,className:`drag-handle`,"data-testid":`drag-handle`,"aria-label":n,...e,...t,children:(0,v.jsx)(`span`,{className:`drag-handle__icon`,"data-testid":`drag-handle-icon`,"aria-hidden":`true`})})}function Ho({isCollapsed:e,onToggle:t,label:n}){return(0,v.jsx)(`button`,{type:`button`,className:`collapse-toggle${e?` collapse-toggle--collapsed`:``}`,"aria-expanded":!e,"aria-label":e?h(`WeDevelopGrid.CollapseToggle.EXPAND_LABEL`,`Expand {label}`,{label:n}):h(`WeDevelopGrid.CollapseToggle.COLLAPSE_LABEL`,`Collapse {label}`,{label:n}),"data-testid":`collapse-toggle`,onClick:e=>{e.stopPropagation(),t()},children:(0,v.jsx)(`span`,{className:`collapse-toggle__chevron`,"aria-hidden":`true`})})}function Uo(e){if(!Ri(e)||e.children===null)return 0;let t=0;for(let n of e.children)t+=1+Uo(n);return t}function Wo(e,t){return t===0?h(`WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_SIMPLE`,`Archive "{title}"?`,{title:e}):t===1?h(`WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_ONE_CHILD`,`Archive "{title}" and all {count} child element?`,{title:e,count:t}):h(`WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_MANY_CHILDREN`,`Archive "{title}" and all {count} child elements?`,{title:e,count:t})}function Go(t){let{pageId:n,zone:r}=Ao(),i=no(n,r),[a,o]=(0,e.useState)(!1),s=Uo(t),c=(0,e.useCallback)(()=>{o(!0)},[]),l=(0,e.useCallback)(()=>{o(!1)},[]),u=(0,e.useCallback)(()=>{o(!1),i.mutate(t.id,{onError:e=>{p(e.message)}})},[i,t.id]);return t.canDelete?{action:{key:`archive`,label:h(`WeDevelopGrid.useArchiveAction.ACTION_LABEL`,`Archive`),destructive:!0,onAction:c},dialog:{isOpen:a,title:h(`WeDevelopGrid.useArchiveAction.DIALOG_TITLE`,`Confirm archive`),message:Wo(t.title,s),onConfirm:u,onCancel:l}}:{action:null,dialog:null}}function Ko(t){let{pageId:n,zone:r}=Ao(),i=ro(n,r),a=(0,e.useCallback)(()=>{i.mutate(t.id,{onError:e=>{p(e.message)}})},[i,t.id]);return t.canCreate?{action:{key:`duplicate`,label:h(`WeDevelopGrid.useDuplicateAction.ACTION_LABEL`,`Duplicate`),onAction:a}}:{action:null}}function qo(e){return Ri(e)?e.containerType:`element`}function Jo(t){let{pageId:n,zone:r}=Ao(),i=io(n,r),[a,o]=(0,e.useState)(!1),[s,c]=(0,e.useState)(null),l=(0,e.useCallback)(()=>{c(null),o(!0)},[]),u=(0,e.useCallback)(()=>{o(!1),c(null)},[]),d=(0,e.useCallback)((e,n,r)=>{i.mutate({id:t.id,targetPageId:e,targetZone:n,targetParent:r},{onSuccess:()=>{o(!1),c(null)},onError:e=>{c(e.message)}})},[i,t.id]);return t.canCreate?{action:{key:`duplicate-to`,label:h(`WeDevelopGrid.useDuplicateToAction.ACTION_LABEL`,`Duplicate to…`),onAction:l},dialog:{isOpen:a,elementType:qo(t),onConfirm:d,onCancel:u,error:s}}:{action:null,dialog:null}}function Yo({actions:t,testId:n=`actions-menu`}){let[r,i]=(0,e.useState)(!1),[a,o]=(0,e.useState)(0),s=(0,e.useRef)(null),c=(0,e.useRef)(null),l=(0,e.useRef)(null),u=(0,e.useId)(),d=(0,e.useCallback)(e=>`${u}item-${e}`,[u]),f=(0,e.useCallback)(()=>i(!1),[]);(0,e.useEffect)(()=>{r&&(o(0),l.current?.focus())},[r]),(0,e.useEffect)(()=>{if(!r)return;function e(e){s.current&&!s.current.contains(e.target)&&f()}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[r,f]),(0,e.useEffect)(()=>{if(!r)return;function e(e){e.key===`Escape`&&(e.stopPropagation(),f(),c.current?.focus())}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[r,f]);function p(e){e.preventDefault(),e.stopPropagation(),i(e=>!e)}function m(e,t){e.preventDefault(),e.stopPropagation(),t(),f()}function g(e){let n=t.length-1;switch(e.key){case`ArrowDown`:e.preventDefault(),o(e=>e>=n?n:e+1);return;case`ArrowUp`:e.preventDefault(),o(e=>e<=0?0:e-1);return;case`Home`:e.preventDefault(),o(0);return;case`End`:e.preventDefault(),o(n);return;case`Enter`:case` `:{e.preventDefault(),e.stopPropagation();let n=t[a];n&&(n.onAction(),f());return}default:return}}if(t.length===0)return null;let _=`${n}-menu`;return(0,v.jsxs)(`div`,{ref:s,className:`actions-menu`,children:[(0,v.jsx)(`button`,{ref:c,type:`button`,className:`actions-menu__trigger`,"data-testid":`actions-menu-trigger`,"aria-haspopup":`menu`,"aria-expanded":r,"aria-controls":r?_:void 0,"aria-label":h(`WeDevelopGrid.ActionsMenu.TRIGGER_LABEL`,`Actions`),onClick:p,children:(0,v.jsx)(`span`,{className:`actions-menu__dots`,"aria-hidden":`true`})}),r&&(0,v.jsx)(`div`,{id:_,ref:l,className:`actions-menu__dropdown`,role:`menu`,tabIndex:-1,"aria-activedescendant":d(a),"data-testid":`${n}-dropdown`,onKeyDown:g,children:t.map((e,t)=>(0,v.jsx)(`div`,{id:d(t),className:`actions-menu__item${e.destructive?` actions-menu__item--destructive`:``}`,role:`menuitem`,tabIndex:t===a?0:-1,onClick:t=>m(t,e.onAction),children:e.label},e.key))})]})}function Xo(e,t=!0){return et({queryKey:Sa.pages.search(e),queryFn:()=>xa(e||void 0),enabled:t})}function Zo(e){return et({queryKey:e===null?[`zones`,`disabled`]:Sa.zones.byPage(e),queryFn:e===null?V:()=>ba(e)})}function Qo(e,t,n){let r=e!==null&&t!==null&&n!==null;return et({queryKey:r?Sa.acceptableContainers.byTarget(e,t,n):[`acceptableContainers`,`disabled`],queryFn:r?()=>ya(e,t,n):V})}var $o={section:`page`,row:`section`,column:`row`,element:`column`};function es({isOpen:t,elementType:n,currentPageId:r,onConfirm:i,onCancel:a,error:o}){let s=(0,e.useRef)(null),[c,l]=(0,e.useState)(`page`),[u,d]=(0,e.useState)(``),[f,p]=(0,e.useState)(``),[m,g]=(0,e.useState)(r),[_,y]=(0,e.useState)(null),[b,x]=(0,e.useState)(null);(0,e.useEffect)(()=>{let e=setTimeout(()=>{p(u)},300);return()=>clearTimeout(e)},[u]),(0,e.useEffect)(()=>{t&&(l(`page`),d(``),p(``),g(r),y(null),x(null))},[t,r]),(0,e.useEffect)(()=>{let e=s.current;e!==null&&(t&&!e.open?e.showModal():!t&&e.open&&e.close())},[t]);let S=(0,e.useCallback)(()=>{a()},[a]),C=Xo(f,c===`page`),w=Zo(c===`page`?null:m),T=Qo(c===`container`?m:null,c===`container`?_:null,c===`container`?n:null),E=(0,e.useCallback)(e=>{g(e)},[]),ee=(0,e.useCallback)(()=>{l(`zone`)},[]);(0,e.useEffect)(()=>{c===`zone`&&w.data!==void 0&&w.data.length===1&&(y(w.data[0]),l(n===`section`?`confirm`:`container`))},[c,w.data,n]);let D=(0,e.useCallback)(e=>{y(e)},[]),O=(0,e.useCallback)(()=>{_!==null&&l(n===`section`?`confirm`:`container`)},[n,_]),k=(0,e.useCallback)(e=>{x(e)},[]),A=(0,e.useCallback)(()=>{_!==null&&(c===`confirm`?i(m,_,{type:`page`,id:m}):b!==null&&i(m,_,{type:$o[n]??`column`,id:b}))},[i,m,_,b,c,n]),te=(0,e.useCallback)(()=>{c===`zone`?(l(`page`),y(null)):c===`container`?(l(`zone`),x(null)):c===`confirm`&&(y(null),l(w.data!==void 0&&w.data.length===1?`page`:`zone`))},[c,w.data]);return(0,v.jsxs)(`dialog`,{ref:s,className:`duplicate-to-dialog`,"data-testid":`duplicate-to-dialog`,onClose:S,onClick:e=>{e.preventDefault(),e.stopPropagation()},children:[(0,v.jsx)(`div`,{className:`duplicate-to-dialog__header`,children:(0,v.jsxs)(`h3`,{className:`duplicate-to-dialog__title`,children:[c===`page`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_PAGE_TITLE`,`Select target page`),c===`zone`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_ZONE_TITLE`,`Select zone`),c===`container`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_CONTAINER_TITLE`,`Select container`),c===`confirm`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_CONFIRM_TITLE`,`Confirm duplication`)]})}),(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__body`,children:[c===`page`&&(0,v.jsxs)(`div`,{"data-testid":`duplicate-to-step-page`,children:[(0,v.jsx)(`input`,{type:`text`,className:`duplicate-to-dialog__search`,"data-testid":`duplicate-to-search`,placeholder:h(`WeDevelopGrid.DuplicateToDialog.SEARCH_PLACEHOLDER`,`Search pages…`),value:u,onChange:e=>d(e.target.value)}),C.isLoading&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__loading`,children:h(`WeDevelopGrid.DuplicateToDialog.LOADING_PAGES`,`Loading pages…`)}),C.data!==void 0&&(0,v.jsx)(`div`,{className:`duplicate-to-dialog__list`,"data-testid":`duplicate-to-page-list`,role:`listbox`,children:C.data.map(e=>(0,v.jsx)(`div`,{className:`duplicate-to-dialog__item${e.id===m?` duplicate-to-dialog__item--selected`:``}${e.hasGridZones?``:` duplicate-to-dialog__item--disabled`}`,role:`option`,"aria-selected":e.id===m,"aria-disabled":!e.hasGridZones,"data-testid":`duplicate-to-page-item`,onClick:e.hasGridZones?()=>E(e.id):void 0,onKeyDown:e.hasGridZones?t=>{(t.key===`Enter`||t.key===` `)&&E(e.id)}:void 0,tabIndex:e.hasGridZones?0:-1,children:e.title},e.id))})]}),c===`zone`&&(0,v.jsxs)(`div`,{"data-testid":`duplicate-to-step-zone`,children:[w.isLoading&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__loading`,children:h(`WeDevelopGrid.DuplicateToDialog.LOADING_ZONES`,`Loading zones…`)}),w.data!==void 0&&w.data.length>1&&(0,v.jsx)(`div`,{className:`duplicate-to-dialog__list`,"data-testid":`duplicate-to-zone-list`,role:`listbox`,children:w.data.map(e=>(0,v.jsx)(`div`,{className:`duplicate-to-dialog__item${e===_?` duplicate-to-dialog__item--selected`:``}`,role:`option`,"aria-selected":e===_,"data-testid":`duplicate-to-zone-item`,onClick:()=>D(e),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&D(e)},tabIndex:0,children:e},e))})]}),c===`container`&&(0,v.jsxs)(`div`,{"data-testid":`duplicate-to-step-container`,children:[T.isLoading&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__loading`,children:h(`WeDevelopGrid.DuplicateToDialog.LOADING_CONTAINERS`,`Loading containers…`)}),T.data!==void 0&&T.data.length===0&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__empty`,"data-testid":`duplicate-to-no-containers`,children:h(`WeDevelopGrid.DuplicateToDialog.NO_CONTAINERS`,`No compatible containers found in this zone`)}),T.data!==void 0&&T.data.length>0&&(0,v.jsx)(`div`,{className:`duplicate-to-dialog__list`,"data-testid":`duplicate-to-container-list`,role:`listbox`,children:T.data.map(e=>(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__item${e.id===b?` duplicate-to-dialog__item--selected`:``}`,role:`option`,"aria-selected":e.id===b,"data-testid":`duplicate-to-container-item`,onClick:()=>k(e.id),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&k(e.id)},tabIndex:0,children:[(0,v.jsx)(`span`,{className:`duplicate-to-dialog__item-title`,children:e.title}),(0,v.jsx)(`span`,{className:`duplicate-to-dialog__item-type`,children:e.type})]},e.id))})]}),c===`confirm`&&(0,v.jsx)(`div`,{"data-testid":`duplicate-to-step-confirm`,children:(0,v.jsxs)(`p`,{className:`duplicate-to-dialog__summary`,children:[h(`WeDevelopGrid.DuplicateToDialog.CONFIRM_SUMMARY_PREFIX`,`Duplicate section to zone`),` `,(0,v.jsx)(`strong`,{children:_}),`?`]})})]}),(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__footer`,children:[o!=null&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__error`,"data-testid":`duplicate-to-error`,children:o}),(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__actions`,children:[c!==`page`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--back`,"data-testid":`duplicate-to-back`,onClick:te,children:h(`WeDevelopGrid.DuplicateToDialog.BACK_BUTTON`,`Back`)}),(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--cancel`,onClick:S,children:h(`WeDevelopGrid.DuplicateToDialog.CANCEL_BUTTON`,`Cancel`)}),c===`page`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--next`,"data-testid":`duplicate-to-next`,disabled:m===0,onClick:ee,children:h(`WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON`,`Next`)}),c===`zone`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--next`,"data-testid":`duplicate-to-next`,disabled:_===null,onClick:O,children:h(`WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON`,`Next`)}),c===`container`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--confirm`,"data-testid":`duplicate-to-confirm`,disabled:b===null,onClick:A,children:h(`WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON`,`Confirm`)}),c===`confirm`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--confirm`,"data-testid":`duplicate-to-confirm`,onClick:A,children:h(`WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON`,`Confirm`)})]})]})]})}function ts({node:e}){let{pageId:t}=Ao(),{action:n,dialog:r}=Go(e),{action:i}=Ko(e),{action:a,dialog:o}=Jo(e);return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(Yo,{actions:[...i===null?[]:[i],...a===null?[]:[a],...n===null?[]:[n]]}),r?.isOpen&&(0,v.jsx)(Fo,{isOpen:r.isOpen,title:r.title,message:r.message,confirmLabel:h(`WeDevelopGrid.ElementActions.ARCHIVE_CONFIRM_LABEL`,`Archive`),onConfirm:r.onConfirm,onCancel:r.onCancel,destructive:!0}),o?.isOpen&&(0,v.jsx)(es,{isOpen:o.isOpen,elementType:o.elementType,currentPageId:t,onConfirm:o.onConfirm,onCancel:o.onCancel,error:o.error})]})}function ns({label:t,options:n,selectedValue:r,disabled:i,testId:a,onSelect:o}){let[s,c]=(0,e.useState)(!1),[l,u]=(0,e.useState)(0),d=(0,e.useRef)(null),f=(0,e.useRef)(null),p=(0,e.useRef)(null),m=`${a}-listbox`,h=(0,e.useId)(),g=(0,e.useCallback)(e=>`${h}opt-${e}`,[h]),_=(0,e.useCallback)(()=>c(!1),[]);(0,e.useEffect)(()=>{if(!s)return;let e=n.findIndex(e=>e.value===r);u(e>=0?e:0),p.current?.focus()},[s,n,r]),(0,e.useEffect)(()=>{if(!s)return;function e(e){d.current&&!d.current.contains(e.target)&&_()}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[s,_]),(0,e.useEffect)(()=>{if(!s)return;function e(e){e.key===`Escape`&&(e.stopPropagation(),_(),f.current?.focus())}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[s,_]);function y(e){e.stopPropagation(),i||c(e=>!e)}function b(e){o(e),_()}function x(e){let t=n.length-1;switch(e.key){case`ArrowDown`:e.preventDefault(),u(e=>e>=t?t:e+1);return;case`ArrowUp`:e.preventDefault(),u(e=>e<=0?0:e-1);return;case`Home`:e.preventDefault(),u(0);return;case`End`:e.preventDefault(),u(t);return;case`Enter`:case` `:{e.preventDefault();let t=n[l];t&&b(t.value);return}default:return}}return(0,v.jsxs)(`div`,{ref:d,className:`grid-settings-picker`,children:[(0,v.jsx)(`button`,{ref:f,type:`button`,className:`grid-settings-picker__trigger${i?` grid-settings-picker__trigger--disabled`:``}`,"data-testid":a,"aria-haspopup":`listbox`,"aria-expanded":s,"aria-controls":s?m:void 0,disabled:i,onClick:y,children:t}),s&&(0,v.jsx)(`div`,{id:m,ref:p,className:`grid-settings-picker__options`,role:`listbox`,tabIndex:-1,"aria-activedescendant":g(l),"data-testid":`${a}-listbox`,onKeyDown:x,children:n.map((e,t)=>(0,v.jsx)(`div`,{id:g(t),className:`grid-settings-picker__option${e.value===r?` grid-settings-picker__option--selected`:``}${e.value===`hidden`?` grid-settings-picker__option--separator`:``}`,role:`option`,"aria-selected":e.value===r,tabIndex:t===l?0:-1,onClick:()=>b(e.value),children:e.label},e.value))})]})}function rs({element:e}){return No()?(0,v.jsx)(as,{element:e}):(0,v.jsx)(is,{element:e})}function is({element:e}){let{attributes:t,listeners:n,setNodeRef:r,transform:i,transition:a,isDragging:o}=Ii({id:e.nodeKey}),s=Lo(e.statusFlags),c=e.editLink,l=c!==null,u=zo(i,a,o),d=[`element-card`,`element-card--${s}`,...l?[`element-card--clickable`]:[]].join(` `),f=(0,v.jsxs)(`div`,{className:`element-card__header`,children:[(0,v.jsx)(Vo,{listeners:n,attributes:t,label:h(`WeDevelopGrid.ElementCard.MOVE_LABEL`,`Move {title}`,{title:e.title})}),(0,v.jsx)(`i`,{className:`element-card__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h4`,{className:`element-card__title`,"data-testid":`element-card-title`,children:e.title}),(0,v.jsx)(ts,{node:e})]});return l?(0,v.jsx)(`a`,{ref:r,href:c,style:u,className:d,"data-testid":`element-card`,onClick:e=>{if(o){e.preventDefault();return}if(!(e.target instanceof Element))return;let t=e.target.closest(`button, input, select, textarea, [role="button"], [role="menuitem"], [role="listbox"], [role="dialog"]`);t!==null&&e.currentTarget.contains(t)&&e.preventDefault()},children:f}):(0,v.jsx)(`div`,{ref:r,style:u,className:d,"data-testid":`element-card`,children:f})}function as({element:e}){return(0,v.jsx)(`div`,{className:`element-card element-card--${Lo(e.statusFlags)}`,"data-testid":`element-card`,children:(0,v.jsxs)(`div`,{className:`element-card__header`,children:[(0,v.jsx)(`i`,{className:`element-card__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h4`,{className:`element-card__title`,"data-testid":`element-card-title`,children:e.title})]})})}function os({message:e,variant:t}){return(0,v.jsx)(`div`,{className:t===`centered`?`empty-state empty-state--centered`:`empty-state`,children:e})}function ss({allowedTypes:t,isOpen:n,onClose:r,onSelect:i}){let a=(0,e.useRef)(null);(0,e.useEffect)(()=>{let e=a.current;e!==null&&(n&&!e.open?e.showModal():!n&&e.open&&e.close())},[n]);let o=(0,e.useCallback)(()=>{r()},[r]),s=(0,e.useCallback)(e=>{i(e),r()},[i,r]),c=Object.entries(t);return(0,v.jsxs)(`dialog`,{ref:a,className:`element-type-picker`,"data-testid":`element-type-picker`,onClose:o,children:[(0,v.jsxs)(`div`,{className:`element-type-picker__header`,children:[(0,v.jsx)(`h3`,{className:`element-type-picker__title`,children:h(`WeDevelopGrid.ElementTypePicker.TITLE`,`Add content element`)}),(0,v.jsx)(`button`,{type:`button`,className:`element-type-picker__close`,"data-testid":`element-type-picker-close`,onClick:o,"aria-label":h(`WeDevelopGrid.ElementTypePicker.CLOSE_LABEL`,`Close`),children:`×`})]}),(0,v.jsx)(`div`,{className:`element-type-picker__body`,children:c.length>0?(0,v.jsx)(`div`,{className:`element-type-picker__grid`,children:c.map(([e,t])=>(0,v.jsxs)(`button`,{type:`button`,className:`element-type-picker__tile`,"data-testid":`element-type-tile`,onClick:()=>s(e),children:[(0,v.jsx)(`span`,{className:`element-type-picker__icon ${t.icon}`}),(0,v.jsx)(`span`,{className:`element-type-picker__label`,children:t.label}),t.description!==``&&(0,v.jsx)(`span`,{className:`element-type-picker__description`,children:t.description})]},e))}):(0,v.jsx)(`p`,{className:`element-type-picker__empty`,children:h(`WeDevelopGrid.ElementTypePicker.EMPTY_MESSAGE`,`No content element types available`)})})]})}function cs(t){let{isCollapsed:n,toggle:r}=mo();return{isCollapsed:n(t.nodeKey),onToggle:(0,e.useCallback)(()=>r(t.nodeKey),[r,t.nodeKey])}}function ls(t){return(0,e.useMemo)(()=>t.children?.map(e=>e.nodeKey)??[],[t.children])}function us({column:e}){return No()?(0,v.jsx)(ps,{column:e}):(0,v.jsx)(fs,{column:e})}function ds(e,t){let n=bo();return xo()===`margin`?{...t,"--col-width":`${e.width/n*100}%`,...e.offset>0?{"--col-offset":`${e.offset/n*100}%`}:{}}:{...t,"--col-span":String(e.width),...e.offset>0?{"--col-start":String(e.offset+1)}:{}}}function fs({column:t}){let{activeViewport:n}=Do(),{pageId:r,zone:i}=Ao(),a=bo(),o=wo(t.gridSettings,n),s=Lo(t.statusFlags),{isCollapsed:c,onToggle:l}=cs(t),{activeType:u}=Ja(),d=ao(r,i),f=to(r,i),[p,m]=(0,e.useState)(!1),{attributes:g,listeners:_,setNodeRef:y,transform:b,transition:x,isDragging:S,isOver:C}=Ii({id:t.nodeKey}),w=ls(t),T=C&&u===`column`,E=u!==null||d.isPending,ee=Bo(`column-block`,s,{hidden:!o.visible,collapsed:c,"drop-target":T}),D=ds(o,zo(b,x,S)),O=So(),k=Co(o.width),A=o.visible?`${o.width}/${a}`:`hidden`,te=o.visible?o.width:`hidden`,ne=o.offset===0?`none`:`+${o.offset}`,j=E||o.width===a||!o.visible,M=(0,e.useCallback)(e=>{d.mutate({id:t.id,viewport:n,...o,...e})},[t.id,n,o,d]),N=(0,e.useCallback)(e=>{M(e===`hidden`?{visible:!1}:{width:e,visible:!0,offset:Math.min(o.offset,a-e)})},[M,a,o.offset]),re=(0,e.useCallback)(e=>{typeof e==`number`&&M({offset:e})},[M]),P=(0,e.useCallback)(()=>{m(!0)},[]),F=(0,e.useCallback)(()=>{m(!1)},[]),ie=(0,e.useCallback)(e=>{f.mutate({className:e,parentId:t.id})},[f,t.id]),I=t.children??[],L=t.allowedTypes??{},R=I.length>0,z=Object.keys(L).length>0;return(0,v.jsxs)(`div`,{ref:y,style:D,className:`row-block__column`,children:[(0,v.jsxs)(`div`,{className:ee,"data-testid":`column-block`,children:[(0,v.jsxs)(`div`,{className:`column-block__header`,"data-testid":`column-header`,children:[(0,v.jsx)(Vo,{listeners:_,attributes:g,label:h(`WeDevelopGrid.ColumnBlock.MOVE_LABEL`,`Move {title}`,{title:t.title})}),(0,v.jsx)(Ho,{isCollapsed:c,onToggle:l,label:t.title}),(0,v.jsx)(`i`,{className:`column-block__icon ${t.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`column-block__title`,"data-testid":`column-title`,children:t.editLink===null?t.title:(0,v.jsx)(`a`,{href:t.editLink,"data-testid":`column-edit-link`,children:t.title})}),(0,v.jsxs)(`div`,{className:`column-block__header-meta`,children:[(0,v.jsx)(ns,{label:A,options:O,selectedValue:te,disabled:E,testId:`column-badge`,onSelect:N}),(0,v.jsx)(ns,{label:ne,options:k,selectedValue:o.offset,disabled:j,testId:`column-offset-badge`,onSelect:re}),(0,v.jsx)(ts,{node:t})]})]}),(0,v.jsxs)(`div`,{className:`column-block__body`,children:[(0,v.jsx)(Oi,{items:w,strategy:wi,children:R?I.map(e=>(0,v.jsx)(rs,{element:e},e.nodeKey)):!z&&(0,v.jsx)(os,{message:h(`WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS`,`No content blocks`)})}),z&&(0,v.jsx)(`button`,{type:`button`,className:`column-block__add-button`,"data-testid":`add-content-button`,onClick:P,children:h(`WeDevelopGrid.ColumnBlock.ADD_CONTENT_BUTTON`,`+ Add content`)})]})]}),z&&(0,v.jsx)(ss,{allowedTypes:L,isOpen:p,onClose:F,onSelect:ie})]})}function ps({column:e}){let{activeViewport:t}=Do(),n=wo(e.gridSettings,t),r=Lo(e.statusFlags),{isCollapsed:i,onToggle:a}=cs(e),o=Bo(`column-block`,r,{hidden:!n.visible,collapsed:i}),s=ds(n,{}),c=e.children??[];return(0,v.jsx)(`div`,{style:s,className:`row-block__column`,children:(0,v.jsxs)(`div`,{className:o,"data-testid":`column-block`,children:[(0,v.jsxs)(`div`,{className:`column-block__header`,"data-testid":`column-header`,children:[(0,v.jsx)(Ho,{isCollapsed:i,onToggle:a,label:e.title}),(0,v.jsx)(`i`,{className:`column-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`column-block__title`,"data-testid":`column-title`,children:e.title})]}),(0,v.jsx)(`div`,{className:`column-block__body`,children:c.length>0?c.map(e=>(0,v.jsx)(rs,{element:e},e.nodeKey)):(0,v.jsx)(os,{message:h(`WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS`,`No content blocks`)})})]})})}var ms={section:`page`,row:`section`,column:`row`};function hs({parentId:e,childType:t,childLabel:n,variant:r}){let{pageId:i,zone:a}=Ao(),{mutate:o,isPending:s}=eo(i,a);function c(){o({containerType:t,parent:{type:ms[t],id:e},...t===`section`?{zone:a}:{}})}let l=(0,v.jsx)(`button`,{type:`button`,className:`add-child-button__button`,"data-testid":`add-child-button`,disabled:s,onClick:c,children:s?h(`WeDevelopGrid.AddChildButton.ADDING_LABEL`,`Adding {childLabel}…`,{childLabel:n}):h(`WeDevelopGrid.AddChildButton.ADD_LABEL`,`Add {childLabel}`,{childLabel:n})});return r===`empty-state`?(0,v.jsxs)(`div`,{className:`add-child-button add-child-button--empty-state`,"data-testid":`add-child-empty`,children:[(0,v.jsx)(`p`,{className:`add-child-button__message`,children:h(`WeDevelopGrid.AddChildButton.EMPTY_MESSAGE`,`No {childLabel}s yet`,{childLabel:n.toLowerCase()})}),l]}):(0,v.jsx)(`div`,{className:`add-child-button add-child-button--append`,"data-testid":`add-child-append`,children:l})}function gs({row:e}){return No()?(0,v.jsx)(bs,{row:e}):(0,v.jsx)(ys,{row:e})}function _s(t){let{isCollapsed:n,toggle:r}=mo();return{isCollapsed:n(t.nodeKey),onToggle:(0,e.useCallback)(()=>r(t.nodeKey),[r,t.nodeKey])}}function vs(t){return(0,e.useMemo)(()=>t.children?.map(e=>e.nodeKey)??[],[t.children])}function ys({row:e}){let t=xo()===`margin`?`flex`:`grid`,n=Lo(e.statusFlags),{isCollapsed:r,onToggle:i}=_s(e),{activeType:a}=Ja(),{attributes:o,listeners:s,setNodeRef:c,transform:l,transition:u,isDragging:d,isOver:f}=Ii({id:e.nodeKey}),p=Bo(`row-block`,n,{collapsed:r,"drop-target":f&&a===`row`}),m=zo(l,u,d),g=vs(e);return(0,v.jsxs)(`div`,{ref:c,style:m,className:p,"data-testid":`row-block`,children:[(0,v.jsxs)(`div`,{className:`row-block__header`,"data-testid":`row-header`,children:[(0,v.jsx)(Vo,{listeners:s,attributes:o,label:h(`WeDevelopGrid.RowBlock.MOVE_LABEL`,`Move {title}`,{title:e.title})}),(0,v.jsx)(Ho,{isCollapsed:r,onToggle:i,label:e.title}),(0,v.jsx)(`i`,{className:`row-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h3`,{className:`row-block__title`,"data-testid":`row-title`,children:e.editLink===null?e.title:(0,v.jsx)(`a`,{href:e.editLink,"data-testid":`row-edit-link`,children:e.title})}),(0,v.jsx)(ts,{node:e})]}),(0,v.jsx)(`div`,{className:`row-block__columns row-block__columns--${t}`,style:t===`grid`?{"--grid-columns":String(bo())}:void 0,children:(0,v.jsx)(Oi,{items:g,strategy:bi,children:e.children!==null&&e.children.length>0?(0,v.jsxs)(v.Fragment,{children:[e.children.map(e=>(0,v.jsx)(us,{column:e},e.nodeKey)),(0,v.jsx)(hs,{parentId:e.id,childType:`column`,childLabel:`Column`,variant:`append`})]}):(0,v.jsx)(hs,{parentId:e.id,childType:`column`,childLabel:`Column`,variant:`empty-state`})})})]})}function bs({row:e}){let t=xo()===`margin`?`flex`:`grid`,n=Lo(e.statusFlags),{isCollapsed:r,onToggle:i}=_s(e);return(0,v.jsxs)(`div`,{className:Bo(`row-block`,n,{collapsed:r}),"data-testid":`row-block`,children:[(0,v.jsxs)(`div`,{className:`row-block__header`,"data-testid":`row-header`,children:[(0,v.jsx)(Ho,{isCollapsed:r,onToggle:i,label:e.title}),(0,v.jsx)(`i`,{className:`row-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h3`,{className:`row-block__title`,"data-testid":`row-title`,children:e.title})]}),(0,v.jsx)(`div`,{className:`row-block__columns row-block__columns--${t}`,style:t===`grid`?{"--grid-columns":String(bo())}:void 0,children:e.children?.map(e=>(0,v.jsx)(us,{column:e},e.nodeKey))})]})}function xs({section:e}){return No()?(0,v.jsx)(Ts,{section:e}):(0,v.jsx)(ws,{section:e})}function Ss(t){let{isCollapsed:n,toggle:r}=mo();return{isCollapsed:n(t.nodeKey),onToggle:(0,e.useCallback)(()=>r(t.nodeKey),[r,t.nodeKey])}}function Cs(t){return(0,e.useMemo)(()=>t.children?.map(e=>e.nodeKey)??[],[t.children])}function ws({section:e}){let t=Lo(e.statusFlags),{isCollapsed:n,onToggle:r}=Ss(e),{activeType:i}=Ja(),{attributes:a,listeners:o,setNodeRef:s,transform:c,transition:l,isDragging:u,isOver:d}=Ii({id:e.nodeKey}),f=Bo(`section-block`,t,{collapsed:n,"drop-target":d&&i===`section`}),p=zo(c,l,u),m=Cs(e);return(0,v.jsxs)(`section`,{ref:s,style:p,className:f,"data-testid":`section-block`,children:[(0,v.jsxs)(`div`,{className:`section-block__header`,"data-testid":`section-header`,children:[(0,v.jsx)(Vo,{listeners:o,attributes:a,label:h(`WeDevelopGrid.SectionBlock.MOVE_LABEL`,`Move {title}`,{title:e.title})}),(0,v.jsx)(Ho,{isCollapsed:n,onToggle:r,label:e.title}),(0,v.jsx)(`i`,{className:`section-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h2`,{className:`section-block__title`,"data-testid":`section-title`,children:e.editLink===null?e.title:(0,v.jsx)(`a`,{href:e.editLink,"data-testid":`section-edit-link`,children:e.title})}),(0,v.jsx)(ts,{node:e})]}),(0,v.jsx)(`div`,{className:`section-block__body`,children:(0,v.jsx)(Oi,{items:m,strategy:wi,children:e.children!==null&&e.children.length>0?(0,v.jsxs)(v.Fragment,{children:[e.children.map(e=>(0,v.jsx)(gs,{row:e},e.nodeKey)),(0,v.jsx)(hs,{parentId:e.id,childType:`row`,childLabel:`Row`,variant:`append`})]}):(0,v.jsx)(hs,{parentId:e.id,childType:`row`,childLabel:`Row`,variant:`empty-state`})})})]})}function Ts({section:e}){let t=Lo(e.statusFlags),{isCollapsed:n,onToggle:r}=Ss(e);return(0,v.jsxs)(`section`,{className:Bo(`section-block`,t,{collapsed:n}),"data-testid":`section-block`,children:[(0,v.jsxs)(`div`,{className:`section-block__header`,"data-testid":`section-header`,children:[(0,v.jsx)(Ho,{isCollapsed:n,onToggle:r,label:e.title}),(0,v.jsx)(`i`,{className:`section-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h2`,{className:`section-block__title`,"data-testid":`section-title`,children:e.title})]}),(0,v.jsx)(`div`,{className:`section-block__body`,children:e.children?.map(e=>(0,v.jsx)(gs,{row:e},e.nodeKey))})]})}function Es(e,t){return e===1?`${e} ${t}`:`${e} ${t}s`}function Ds(e){return Ri(e)?e.children?.length??0:0}function Os({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title}),(0,v.jsx)(`span`,{className:`drag-overlay-content__meta`,children:Es(Ds(e),h(`WeDevelopGrid.DragOverlayContent.ROW_SINGULAR`,`row`))})]})}function ks({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title}),(0,v.jsx)(`span`,{className:`drag-overlay-content__meta`,children:Es(Ds(e),h(`WeDevelopGrid.DragOverlayContent.COLUMN_SINGULAR`,`column`))})]})}function As({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title})]})}function js({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title})]})}var Ms={section:Os,row:ks,column:As,element:js};function Ns({node:e,type:t}){let n=Ms[t];return(0,v.jsx)(`div`,{className:`drag-overlay-content drag-overlay-content--${t}`,"data-testid":`drag-overlay-${t}`,children:(0,v.jsx)(n,{node:e,type:t})})}function Ps(e){return{rootParent:{type:`page`,id:e??1},nodes:[],overrideCounts:{}}}function Fs({pageId:t,zone:n,readonly:r=!1,version:i}){let{data:a,isLoading:o,error:s}=wa(t,n,r?i:void 0),c=so(t??0,n),l=a??Ps(t),{dndContextProps:u,dragState:d,pendingTree:f}=Za({tree:l,onReorder:(e,t,n,r)=>{c.mutate({params:{element:e,parent:t,after:n},tree:l,clearPendingTree:r})}}),p=f??a,m=p===void 0?[]:p.nodes.filter(zi),g=fo(t??0),_=(0,e.useMemo)(()=>m.map(e=>e.nodeKey),[m]),y=(0,e.useMemo)(()=>({activeType:d?.activeType??null}),[d?.activeType]),b=r?`grid-editor grid-editor--readonly`:`grid-editor`,x=m.length>0,S=x?m.map(e=>(0,v.jsx)(xs,{section:e},e.nodeKey)):r?(0,v.jsx)(`p`,{className:`grid-editor__empty-state`,children:h(`WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY`,`No sections in this version`)}):t!==null&&(0,v.jsx)(hs,{parentId:t,childType:`section`,childLabel:`Section`,variant:`empty-state`});return(0,v.jsxs)(`div`,{className:b,"data-page-id":t??void 0,"data-zone":n,"data-testid":`grid-editor`,children:[o&&(0,v.jsx)(`p`,{className:`grid-editor__loading`,"data-testid":`grid-editor-loading`,children:h(`WeDevelopGrid.GridEditor.LOADING`,`Loading elements...`)}),s!==null&&(0,v.jsx)(`p`,{className:`grid-editor__error`,children:h(`WeDevelopGrid.GridEditor.LOAD_ERROR`,`Failed to load elements: {message}`,{message:s.message})}),a!==void 0&&t!==null&&(0,v.jsx)(ko,{value:{pageId:t,zone:n},children:(0,v.jsx)(Eo,{children:(0,v.jsx)(Mo,{value:r,children:(0,v.jsxs)(po.Provider,{value:g,children:[(0,v.jsx)(Io,{}),r?S:(0,v.jsxs)(qr,{...u,measuring:{droppable:{strategy:lr.Always}},children:[(0,v.jsx)(qa.Provider,{value:y,children:(0,v.jsxs)(Oi,{items:_,strategy:wi,children:[S,x&&(0,v.jsx)(hs,{parentId:t,childType:`section`,childLabel:`Section`,variant:`append`})]})}),(0,v.jsx)(pi,{children:d!==null&&(0,v.jsx)(Ns,{node:d.activeNode,type:d.activeType})})]})]})})})}),a!==void 0&&t===null&&(0,v.jsx)(os,{message:h(`WeDevelopGrid.GridEditor.NO_SECTIONS`,`No sections yet`),variant:`centered`})]})}function Is({data:e,readOnly:t}){let n=typeof e?.pageId==`number`?e.pageId:null,r=typeof e?.zone==`string`?e.zone:`main`,i=typeof e?.version==`number`&&e.version>0?e.version:void 0,a=t===!0||e?.readonly===!0;return n===null?null:(0,v.jsx)(nt,{children:(0,v.jsx)(y,{children:(0,v.jsx)(Fs,{pageId:n,zone:r,readonly:a,version:i})})})}function Ls(){rt().component.registerMany({GridEditor:Fs,GridEditorField:Is})}document.addEventListener(`DOMContentLoaded`,()=>{try{Ls()}catch(e){console.warn(`[GridEditor] Failed to register components.`,e)}})})(React,ReactDomClient,ReactDom); \ No newline at end of file + `},en={onDragStart(e){let{active:t}=e;return`Picked up draggable item `+t.id+`.`},onDragOver(e){let{active:t,over:n}=e;return n?`Draggable item `+t.id+` was moved over droppable area `+n.id+`.`:`Draggable item `+t.id+` is no longer over a droppable area.`},onDragEnd(e){let{active:t,over:n}=e;return n?`Draggable item `+t.id+` was dropped over droppable area `+n.id:`Draggable item `+t.id+` was dropped.`},onDragCancel(e){let{active:t}=e;return`Dragging was cancelled. Draggable item `+t.id+` was dropped.`}};function tn(t){let{announcements:r=en,container:i,hiddenTextDescribedById:a,screenReaderInstructions:o=$t}=t,{announce:s,announcement:c}=Yt(),l=Ft(`DndLiveRegion`),[u,d]=(0,e.useState)(!1);if((0,e.useEffect)(()=>{d(!0)},[]),Zt((0,e.useMemo)(()=>({onDragStart(e){let{active:t}=e;s(r.onDragStart({active:t}))},onDragMove(e){let{active:t,over:n}=e;r.onDragMove&&s(r.onDragMove({active:t,over:n}))},onDragOver(e){let{active:t,over:n}=e;s(r.onDragOver({active:t,over:n}))},onDragEnd(e){let{active:t,over:n}=e;s(r.onDragEnd({active:t,over:n}))},onDragCancel(e){let{active:t,over:n}=e;s(r.onDragCancel({active:t,over:n}))}}),[s,r])),!u)return null;let f=e.default.createElement(e.default.Fragment,null,e.default.createElement(qt,{id:a,value:o.draggable}),e.default.createElement(Jt,{id:l,announcement:c}));return i?(0,n.createPortal)(f,i):f}var V;(function(e){e.DragStart=`dragStart`,e.DragMove=`dragMove`,e.DragEnd=`dragEnd`,e.DragCancel=`dragCancel`,e.DragOver=`dragOver`,e.RegisterDroppable=`registerDroppable`,e.SetDroppableDisabled=`setDroppableDisabled`,e.UnregisterDroppable=`unregisterDroppable`})(V||={});function nn(){}function rn(t,n){return(0,e.useMemo)(()=>({sensor:t,options:n??{}}),[t,n])}function an(){for(var t=arguments.length,n=Array(t),r=0;r[...n].filter(e=>e!=null),[...n])}var on=Object.freeze({x:0,y:0});function sn(e,t){return Math.sqrt((e.x-t.x)**2+(e.y-t.y)**2)}function cn(e,t){let n=Ht(e);if(!n)return`0 0`;let r={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return r.x+`% `+r.y+`%`}function ln(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function un(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function dn(e,t){if(!e||e.length===0)return null;let[n]=e;return t?n[t]:n}function fn(e,t,n){return t===void 0&&(t=e.left),n===void 0&&(n=e.top),{x:t+e.width*.5,y:n+e.height*.5}}var pn=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e,i=fn(t,t.left,t.top),a=[];for(let e of r){let{id:t}=e,r=n.get(t);if(r){let n=sn(fn(r),i);a.push({id:t,data:{droppableContainer:e,value:n}})}}return a.sort(ln)};function mn(e,t){let n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),i=Math.min(t.left+t.width,e.left+e.width),a=Math.min(t.top+t.height,e.top+e.height),o=i-r,s=a-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e,i=[];for(let e of r){let{id:r}=e,a=n.get(r);if(a){let n=mn(a,t);n>0&&i.push({id:r,data:{droppableContainer:e,value:n}})}}return i.sort(un)};function gn(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function _n(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:on}function vn(e){return function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x}),{...t})}}var yn=vn(1);function bn(e){if(e.startsWith(`matrix3d(`)){let t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith(`matrix(`)){let t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function xn(e,t,n){let r=bn(t);if(!r)return e;let{scaleX:i,scaleY:a,x:o,y:s}=r,c=e.left-o-(1-i)*parseFloat(n),l=e.top-s-(1-a)*parseFloat(n.slice(n.indexOf(` `)+1)),u=i?e.width/i:e.width,d=a?e.height/a:e.height;return{width:u,height:d,top:l,right:c+u,bottom:l+d,left:c}}var Sn={ignoreTransform:!1};function Cn(e,t){t===void 0&&(t=Sn);let n=e.getBoundingClientRect();if(t.ignoreTransform){let{transform:t,transformOrigin:r}=B(e).getComputedStyle(e);t&&(n=xn(n,t,r))}let{top:r,left:i,width:a,height:o,bottom:s,right:c}=n;return{top:r,left:i,width:a,height:o,bottom:s,right:c}}function wn(e){return Cn(e,{ignoreTransform:!0})}function Tn(e){let t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function En(e,t){return t===void 0&&(t=B(e).getComputedStyle(e)),t.position===`fixed`}function Dn(e,t){t===void 0&&(t=B(e).getComputedStyle(e));let n=/(auto|scroll|overlay)/;return[`overflow`,`overflowX`,`overflowY`].some(e=>{let r=t[e];return typeof r==`string`?n.test(r):!1})}function On(e,t){let n=[];function r(i){if(t!=null&&n.length>=t||!i)return n;if(Ct(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!wt(i)||Tt(i)||n.includes(i))return n;let a=B(e).getComputedStyle(i);return i!==e&&Dn(i,a)&&n.push(i),En(i,a)?n:r(i.parentNode)}return e?r(e):n}function kn(e){let[t]=On(e,1);return t??null}function An(e){return!bt||!e?null:xt(e)?e:St(e)?Ct(e)||e===Et(e).scrollingElement?window:wt(e)?e:null:null}function jn(e){return xt(e)?e.scrollX:e.scrollLeft}function Mn(e){return xt(e)?e.scrollY:e.scrollTop}function Nn(e){return{x:jn(e),y:Mn(e)}}var H;(function(e){e[e.Forward=1]=`Forward`,e[e.Backward=-1]=`Backward`})(H||={});function Pn(e){return!bt||!e?!1:e===document.scrollingElement}function Fn(e){let t={x:0,y:0},n=Pn(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=r.y,isRight:e.scrollLeft>=r.x,maxScroll:r,minScroll:t}}var In={x:.2,y:.2};function Ln(e,t,n,r,i){let{top:a,left:o,right:s,bottom:c}=n;r===void 0&&(r=10),i===void 0&&(i=In);let{isTop:l,isBottom:u,isLeft:d,isRight:f}=Fn(e),p={x:0,y:0},m={x:0,y:0},h={height:t.height*i.y,width:t.width*i.x};return!l&&a<=t.top+h.height?(p.y=H.Backward,m.y=r*Math.abs((t.top+h.height-a)/h.height)):!u&&c>=t.bottom-h.height&&(p.y=H.Forward,m.y=r*Math.abs((t.bottom-h.height-c)/h.height)),!f&&s>=t.right-h.width?(p.x=H.Forward,m.x=r*Math.abs((t.right-h.width-s)/h.width)):!d&&o<=t.left+h.width&&(p.x=H.Backward,m.x=r*Math.abs((t.left+h.width-o)/h.width)),{direction:p,speed:m}}function Rn(e){if(e===document.scrollingElement){let{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}let{top:t,left:n,right:r,bottom:i}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:i,width:e.clientWidth,height:e.clientHeight}}function zn(e){return e.reduce((e,t)=>Lt(e,Nn(t)),on)}function Bn(e){return e.reduce((e,t)=>e+jn(t),0)}function Vn(e){return e.reduce((e,t)=>e+Mn(t),0)}function Hn(e,t){if(t===void 0&&(t=Cn),!e)return;let{top:n,left:r,bottom:i,right:a}=t(e);kn(e)&&(i<=0||a<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:`center`,inline:`center`})}var Un=[[`x`,[`left`,`right`],Bn],[`y`,[`top`,`bottom`],Vn]],Wn=class{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;let n=On(t),r=zn(n);this.rect={...e},this.width=e.width,this.height=e.height;for(let[e,t,i]of Un)for(let a of t)Object.defineProperty(this,a,{get:()=>{let t=i(n),o=r[e]-t;return this.rect[a]+o},enumerable:!0});Object.defineProperty(this,`rect`,{enumerable:!1})}},Gn=class{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>this.target?.removeEventListener(...e))},this.target=e}add(e,t,n){var r;(r=this.target)==null||r.addEventListener(e,t,n),this.listeners.push([e,t,n])}};function Kn(e){let{EventTarget:t}=B(e);return e instanceof t?e:Et(e)}function qn(e,t){let n=Math.abs(e.x),r=Math.abs(e.y);return typeof t==`number`?Math.sqrt(n**2+r**2)>t:`x`in t&&`y`in t?n>t.x&&r>t.y:`x`in t?n>t.x:`y`in t?r>t.y:!1}var Jn;(function(e){e.Click=`click`,e.DragStart=`dragstart`,e.Keydown=`keydown`,e.ContextMenu=`contextmenu`,e.Resize=`resize`,e.SelectionChange=`selectionchange`,e.VisibilityChange=`visibilitychange`})(Jn||={});function Yn(e){e.preventDefault()}function Xn(e){e.stopPropagation()}var U;(function(e){e.Space=`Space`,e.Down=`ArrowDown`,e.Right=`ArrowRight`,e.Left=`ArrowLeft`,e.Up=`ArrowUp`,e.Esc=`Escape`,e.Enter=`Enter`,e.Tab=`Tab`})(U||={});var Zn={start:[U.Space,U.Enter],cancel:[U.Esc],end:[U.Space,U.Enter,U.Tab]},Qn=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case U.Right:return{...n,x:n.x+25};case U.Left:return{...n,x:n.x-25};case U.Down:return{...n,y:n.y+25};case U.Up:return{...n,y:n.y-25}}},$n=class{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;let{event:{target:t}}=e;this.props=e,this.listeners=new Gn(Et(t)),this.windowListeners=new Gn(B(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(Jn.Resize,this.handleCancel),this.windowListeners.add(Jn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(Jn.Keydown,this.handleKeyDown))}handleStart(){let{activeNode:e,onStart:t}=this.props,n=e.node.current;n&&Hn(n),t(on)}handleKeyDown(e){if(Bt(e)){let{active:t,context:n,options:r}=this.props,{keyboardCodes:i=Zn,coordinateGetter:a=Qn,scrollBehavior:o=`smooth`}=r,{code:s}=e;if(i.end.includes(s)){this.handleEnd(e);return}if(i.cancel.includes(s)){this.handleCancel(e);return}let{collisionRect:c}=n.current,l=c?{x:c.left,y:c.top}:on;this.referenceCoordinates||=l;let u=a(e,{active:t,context:n.current,currentCoordinates:l});if(u){let t=Rt(u,l),r={x:0,y:0},{scrollableAncestors:i}=n.current;for(let n of i){let i=e.code,{isTop:a,isRight:s,isLeft:c,isBottom:l,maxScroll:d,minScroll:f}=Fn(n),p=Rn(n),m={x:Math.min(i===U.Right?p.right-p.width/2:p.right,Math.max(i===U.Right?p.left:p.left+p.width/2,u.x)),y:Math.min(i===U.Down?p.bottom-p.height/2:p.bottom,Math.max(i===U.Down?p.top:p.top+p.height/2,u.y))},h=i===U.Right&&!s||i===U.Left&&!c,g=i===U.Down&&!l||i===U.Up&&!a;if(h&&m.x!==u.x){let e=n.scrollLeft+t.x,a=i===U.Right&&e<=d.x||i===U.Left&&e>=f.x;if(a&&!t.y){n.scrollTo({left:e,behavior:o});return}a?r.x=n.scrollLeft-e:r.x=i===U.Right?n.scrollLeft-d.x:n.scrollLeft-f.x,r.x&&n.scrollBy({left:-r.x,behavior:o});break}else if(g&&m.y!==u.y){let e=n.scrollTop+t.y,a=i===U.Down&&e<=d.y||i===U.Up&&e>=f.y;if(a&&!t.x){n.scrollTo({top:e,behavior:o});return}a?r.y=n.scrollTop-e:r.y=i===U.Down?n.scrollTop-d.y:n.scrollTop-f.y,r.y&&n.scrollBy({top:-r.y,behavior:o});break}}this.handleMove(e,Lt(Rt(u,this.referenceCoordinates),r))}}}handleMove(e,t){let{onMove:n}=this.props;e.preventDefault(),n(t)}handleEnd(e){let{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){let{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}};$n.activators=[{eventName:`onKeyDown`,handler:(e,t,n)=>{let{keyboardCodes:r=Zn,onActivation:i}=t,{active:a}=n,{code:o}=e.nativeEvent;if(r.start.includes(o)){let t=a.activatorNode.current;return t&&e.target!==t?!1:(e.preventDefault(),i?.({event:e.nativeEvent}),!0)}return!1}}];function er(e){return!!(e&&`distance`in e)}function tr(e){return!!(e&&`delay`in e)}var nr=class{constructor(e,t,n){n===void 0&&(n=Kn(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;let{event:r}=e,{target:i}=r;this.props=e,this.events=t,this.document=Et(i),this.documentListeners=new Gn(this.document),this.listeners=new Gn(n),this.windowListeners=new Gn(B(i)),this.initialCoordinates=Ht(r)??on,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){let{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:n}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(Jn.Resize,this.handleCancel),this.windowListeners.add(Jn.DragStart,Yn),this.windowListeners.add(Jn.VisibilityChange,this.handleCancel),this.windowListeners.add(Jn.ContextMenu,Yn),this.documentListeners.add(Jn.Keydown,this.handleKeydown),t){if(n!=null&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(tr(t)){this.timeoutId=setTimeout(this.handleStart,t.delay),this.handlePending(t);return}if(er(t)){this.handlePending(t);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){let{active:n,onPending:r}=this.props;r(n,e,this.initialCoordinates,t)}handleStart(){let{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(Jn.Click,Xn,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(Jn.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){let{activated:t,initialCoordinates:n,props:r}=this,{onMove:i,options:{activationConstraint:a}}=r;if(!n)return;let o=Ht(e)??on,s=Rt(n,o);if(!t&&a){if(er(a)){if(a.tolerance!=null&&qn(s,a.tolerance))return this.handleCancel();if(qn(s,a.distance))return this.handleStart()}if(tr(a)&&qn(s,a.tolerance))return this.handleCancel();this.handlePending(a,s);return}e.cancelable&&e.preventDefault(),i(o)}handleEnd(){let{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){let{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===U.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}},rr={cancel:{name:`pointercancel`},move:{name:`pointermove`},end:{name:`pointerup`}},ir=class extends nr{constructor(e){let{event:t}=e,n=Et(t.target);super(e,rr,n)}};ir.activators=[{eventName:`onPointerDown`,handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];var ar={move:{name:`mousemove`},end:{name:`mouseup`}},or;(function(e){e[e.RightClick=2]=`RightClick`})(or||={});var sr=class extends nr{constructor(e){super(e,ar,Et(e.event.target))}};sr.activators=[{eventName:`onMouseDown`,handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button===or.RightClick?!1:(r?.({event:n}),!0)}}];var cr={cancel:{name:`touchcancel`},move:{name:`touchmove`},end:{name:`touchend`}},lr=class extends nr{constructor(e){super(e,cr)}static setup(){return window.addEventListener(cr.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(cr.move.name,e)};function e(){}}};lr.activators=[{eventName:`onTouchStart`,handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t,{touches:i}=n;return i.length>1?!1:(r?.({event:n}),!0)}}];var ur;(function(e){e[e.Pointer=0]=`Pointer`,e[e.DraggableRect=1]=`DraggableRect`})(ur||={});var dr;(function(e){e[e.TreeOrder=0]=`TreeOrder`,e[e.ReversedTreeOrder=1]=`ReversedTreeOrder`})(dr||={});function fr(t){let{acceleration:n,activator:r=ur.Pointer,canScroll:i,draggingRect:a,enabled:o,interval:s=5,order:c=dr.TreeOrder,pointerCoordinates:l,scrollableAncestors:u,scrollableAncestorRects:d,delta:f,threshold:p}=t,m=mr({delta:f,disabled:!o}),[h,g]=kt(),_=(0,e.useRef)({x:0,y:0}),v=(0,e.useRef)({x:0,y:0}),y=(0,e.useMemo)(()=>{switch(r){case ur.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case ur.DraggableRect:return a}},[r,a,l]),b=(0,e.useRef)(null),x=(0,e.useCallback)(()=>{let e=b.current;if(!e)return;let t=_.current.x*v.current.x,n=_.current.y*v.current.y;e.scrollBy(t,n)},[]),S=(0,e.useMemo)(()=>c===dr.TreeOrder?[...u].reverse():u,[c,u]);(0,e.useEffect)(()=>{if(!o||!u.length||!y){g();return}for(let e of S){if(i?.(e)===!1)continue;let t=d[u.indexOf(e)];if(!t)continue;let{direction:r,speed:a}=Ln(e,t,y,n,p);for(let e of[`x`,`y`])m[e][r[e]]||(a[e]=0,r[e]=0);if(a.x>0||a.y>0){g(),b.current=e,h(x,s),_.current=a,v.current=r;return}}_.current={x:0,y:0},v.current={x:0,y:0},g()},[n,x,i,g,o,s,JSON.stringify(y),JSON.stringify(m),h,u,S,d,JSON.stringify(p)])}var pr={x:{[H.Backward]:!1,[H.Forward]:!1},y:{[H.Backward]:!1,[H.Forward]:!1}};function mr(e){let{delta:t,disabled:n}=e,r=Nt(t);return jt(e=>{if(n||!r||!e)return pr;let i={x:Math.sign(t.x-r.x),y:Math.sign(t.y-r.y)};return{x:{[H.Backward]:e.x[H.Backward]||i.x===-1,[H.Forward]:e.x[H.Forward]||i.x===1},y:{[H.Backward]:e.y[H.Backward]||i.y===-1,[H.Forward]:e.y[H.Forward]||i.y===1}}},[n,t,r])}function hr(e,t){let n=t==null?void 0:e.get(t),r=n?n.node.current:null;return jt(e=>t==null?null:r??e??null,[r,t])}function gr(t,n){return(0,e.useMemo)(()=>t.reduce((e,t)=>{let{sensor:r}=t,i=r.activators.map(e=>({eventName:e.eventName,handler:n(e.handler,t)}));return[...e,...i]},[]),[t,n])}var _r;(function(e){e[e.Always=0]=`Always`,e[e.BeforeDragging=1]=`BeforeDragging`,e[e.WhileDragging=2]=`WhileDragging`})(_r||={});var vr;(function(e){e.Optimized=`optimized`})(vr||={});var yr=new Map;function br(t,n){let{dragging:r,dependencies:i,config:a}=n,[o,s]=(0,e.useState)(null),{frequency:c,measure:l,strategy:u}=a,d=(0,e.useRef)(t),f=_(),p=At(f),m=(0,e.useCallback)(function(e){e===void 0&&(e=[]),!p.current&&s(t=>t===null?e:t.concat(e.filter(e=>!t.includes(e))))},[p]),h=(0,e.useRef)(null),g=jt(e=>{if(f&&!r)return yr;if(!e||e===yr||d.current!==t||o!=null){let e=new Map;for(let n of t){if(!n)continue;if(o&&o.length>0&&!o.includes(n.id)&&n.rect.current){e.set(n.id,n.rect.current);continue}let t=n.node.current,r=t?new Wn(l(t),t):null;n.rect.current=r,r&&e.set(n.id,r)}return e}return e},[t,o,r,f,l]);return(0,e.useEffect)(()=>{d.current=t},[t]),(0,e.useEffect)(()=>{f||m()},[r,f]),(0,e.useEffect)(()=>{o&&o.length>0&&s(null)},[JSON.stringify(o)]),(0,e.useEffect)(()=>{f||typeof c!=`number`||h.current!==null||(h.current=setTimeout(()=>{m(),h.current=null},c))},[c,f,m,...i]),{droppableRects:g,measureDroppableContainers:m,measuringScheduled:o!=null};function _(){switch(u){case _r.Always:return!1;case _r.BeforeDragging:return r;default:return!r}}}function xr(e,t){return jt(n=>e?n||(typeof t==`function`?t(e):e):null,[t,e])}function Sr(e,t){return xr(e,t)}function Cr(t){let{callback:n,disabled:r}=t,i=Ot(n),a=(0,e.useMemo)(()=>{if(r||typeof window>`u`||window.MutationObserver===void 0)return;let{MutationObserver:e}=window;return new e(i)},[i,r]);return(0,e.useEffect)(()=>()=>a?.disconnect(),[a]),a}function wr(t){let{callback:n,disabled:r}=t,i=Ot(n),a=(0,e.useMemo)(()=>{if(r||typeof window>`u`||window.ResizeObserver===void 0)return;let{ResizeObserver:e}=window;return new e(i)},[r]);return(0,e.useEffect)(()=>()=>a?.disconnect(),[a]),a}function Tr(e){return new Wn(Cn(e),e)}function Er(t,n,r){n===void 0&&(n=Tr);let[i,a]=(0,e.useState)(null);function o(){a(e=>{if(!t)return null;if(t.isConnected===!1)return e??r??null;let i=n(t);return JSON.stringify(e)===JSON.stringify(i)?e:i})}let s=Cr({callback(e){if(t)for(let n of e){let{type:e,target:r}=n;if(e===`childList`&&r instanceof HTMLElement&&r.contains(t)){o();break}}}}),c=wr({callback:o});return Dt(()=>{o(),t?(c?.observe(t),s?.observe(document.body,{childList:!0,subtree:!0})):(c?.disconnect(),s?.disconnect())},[t]),i}function Dr(e){return _n(e,xr(e))}var Or=[];function kr(t){let n=(0,e.useRef)(t),r=jt(e=>t?e&&e!==Or&&t&&n.current&&t.parentNode===n.current.parentNode?e:On(t):Or,[t]);return(0,e.useEffect)(()=>{n.current=t},[t]),r}function Ar(t){let[n,r]=(0,e.useState)(null),i=(0,e.useRef)(t),a=(0,e.useCallback)(e=>{let t=An(e.target);t&&r(e=>e?(e.set(t,Nn(t)),new Map(e)):null)},[]);return(0,e.useEffect)(()=>{let e=i.current;if(t!==e){n(e);let o=t.map(e=>{let t=An(e);return t?(t.addEventListener(`scroll`,a,{passive:!0}),[t,Nn(t)]):null}).filter(e=>e!=null);r(o.length?new Map(o):null),i.current=t}return()=>{n(t),n(e)};function n(e){e.forEach(e=>{An(e)?.removeEventListener(`scroll`,a)})}},[a,t]),(0,e.useMemo)(()=>t.length?n?Array.from(n.values()).reduce((e,t)=>Lt(e,t),on):zn(t):on,[t,n])}function jr(t,n){n===void 0&&(n=[]);let r=(0,e.useRef)(null);return(0,e.useEffect)(()=>{r.current=null},n),(0,e.useEffect)(()=>{let e=t!==on;e&&!r.current&&(r.current=t),!e&&r.current&&(r.current=null)},[t]),r.current?Rt(t,r.current):on}function Mr(t){(0,e.useEffect)(()=>{if(!bt)return;let e=t.map(e=>{let{sensor:t}=e;return t.setup==null?void 0:t.setup()});return()=>{for(let t of e)t?.()}},t.map(e=>{let{sensor:t}=e;return t}))}function Nr(t,n){return(0,e.useMemo)(()=>t.reduce((e,t)=>{let{eventName:r,handler:i}=t;return e[r]=e=>{i(e,n)},e},{}),[t,n])}function Pr(t){return(0,e.useMemo)(()=>t?Tn(t):null,[t])}var Fr=[];function Ir(t,n){n===void 0&&(n=Cn);let[r]=t,i=Pr(r?B(r):null),[a,o]=(0,e.useState)(Fr);function s(){o(()=>t.length?t.map(e=>Pn(e)?i:new Wn(n(e),e)):Fr)}let c=wr({callback:s});return Dt(()=>{c?.disconnect(),s(),t.forEach(e=>c?.observe(e))},[t]),a}function Lr(e){if(!e)return null;if(e.children.length>1)return e;let t=e.children[0];return wt(t)?t:e}function Rr(t){let{measure:n}=t,[r,i]=(0,e.useState)(null),a=wr({callback:(0,e.useCallback)(e=>{for(let{target:t}of e)if(wt(t)){i(e=>{let r=n(t);return e?{...e,width:r.width,height:r.height}:r});break}},[n])}),[o,s]=Mt((0,e.useCallback)(e=>{let t=Lr(e);a?.disconnect(),t&&a?.observe(t),i(t?n(t):null)},[n,a]));return(0,e.useMemo)(()=>({nodeRef:o,rect:r,setRef:s}),[r,o,s])}var zr=[{sensor:ir,options:{}},{sensor:$n,options:{}}],Br={current:{}},Vr={draggable:{measure:wn},droppable:{measure:wn,strategy:_r.WhileDragging,frequency:vr.Optimized},dragOverlay:{measure:Cn}},Hr=class extends Map{get(e){return e==null?void 0:super.get(e)??void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){return this.get(e)?.node.current??void 0}},Ur={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Hr,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:nn},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Vr,measureDroppableContainers:nn,windowRect:null,measuringScheduled:!1},Wr={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:``},dispatch:nn,draggableNodes:new Map,over:null,measureDroppableContainers:nn},Gr=(0,e.createContext)(Wr),Kr=(0,e.createContext)(Ur);function qr(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Hr}}}function Jr(e,t){switch(t.type){case V.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case V.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case V.DragEnd:case V.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case V.RegisterDroppable:{let{element:n}=t,{id:r}=n,i=new Hr(e.droppable.containers);return i.set(r,n),{...e,droppable:{...e.droppable,containers:i}}}case V.SetDroppableDisabled:{let{id:n,key:r,disabled:i}=t,a=e.droppable.containers.get(n);if(!a||r!==a.key)return e;let o=new Hr(e.droppable.containers);return o.set(n,{...a,disabled:i}),{...e,droppable:{...e.droppable,containers:o}}}case V.UnregisterDroppable:{let{id:n,key:r}=t,i=e.droppable.containers.get(n);if(!i||r!==i.key)return e;let a=new Hr(e.droppable.containers);return a.delete(n),{...e,droppable:{...e.droppable,containers:a}}}default:return e}}function Yr(t){let{disabled:n}=t,{active:r,activatorEvent:i,draggableNodes:a}=(0,e.useContext)(Gr),o=Nt(i),s=Nt(r?.id);return(0,e.useEffect)(()=>{if(!n&&!i&&o&&s!=null){if(!Bt(o)||document.activeElement===o.target)return;let e=a.get(s);if(!e)return;let{activatorNode:t,node:n}=e;if(!t.current&&!n.current)return;requestAnimationFrame(()=>{for(let e of[t.current,n.current]){if(!e)continue;let t=Gt(e);if(t){t.focus();break}}})}},[i,n,a,s,o]),null}function Xr(e,t){let{transform:n,...r}=t;return e!=null&&e.length?e.reduce((e,t)=>t({transform:e,...r}),n):n}function Zr(t){return(0,e.useMemo)(()=>({draggable:{...Vr.draggable,...t?.draggable},droppable:{...Vr.droppable,...t?.droppable},dragOverlay:{...Vr.dragOverlay,...t?.dragOverlay}}),[t?.draggable,t?.droppable,t?.dragOverlay])}function Qr(t){let{activeNode:n,measure:r,initialRect:i,config:a=!0}=t,o=(0,e.useRef)(!1),{x:s,y:c}=typeof a==`boolean`?{x:a,y:a}:a;Dt(()=>{if(!s&&!c||!n){o.current=!1;return}if(o.current||!i)return;let e=n?.node.current;if(!e||e.isConnected===!1)return;let t=_n(r(e),i);if(s||(t.x=0),c||(t.y=0),o.current=!0,Math.abs(t.x)>0||Math.abs(t.y)>0){let n=kn(e);n&&n.scrollBy({top:t.y,left:t.x})}},[n,s,c,i,r])}var $r=(0,e.createContext)({...on,scaleX:1,scaleY:1}),ei;(function(e){e[e.Uninitialized=0]=`Uninitialized`,e[e.Initializing=1]=`Initializing`,e[e.Initialized=2]=`Initialized`})(ei||={});var ti=(0,e.memo)(function(t){let{id:r,accessibility:i,autoScroll:a=!0,children:o,sensors:s=zr,collisionDetection:c=hn,measuring:l,modifiers:u,...d}=t,[f,p]=(0,e.useReducer)(Jr,void 0,qr),[m,h]=Qt(),[g,_]=(0,e.useState)(ei.Uninitialized),v=g===ei.Initialized,{draggable:{active:y,nodes:b,translate:x},droppable:{containers:S}}=f,C=y==null?null:b.get(y),w=(0,e.useRef)({initial:null,translated:null}),T=(0,e.useMemo)(()=>y==null?null:{id:y,data:C?.data??Br,rect:w},[y,C]),E=(0,e.useRef)(null),[ee,D]=(0,e.useState)(null),[O,k]=(0,e.useState)(null),A=At(d,Object.values(d)),te=Ft(`DndDescribedBy`,r),ne=(0,e.useMemo)(()=>S.getEnabled(),[S]),j=Zr(l),{droppableRects:M,measureDroppableContainers:N,measuringScheduled:re}=br(ne,{dragging:v,dependencies:[x.x,x.y],config:j.droppable}),P=hr(b,y),F=(0,e.useMemo)(()=>O?Ht(O):null,[O]),ie=je(),ae=Sr(P,j.draggable.measure);Qr({activeNode:y==null?null:b.get(y),config:ie.layoutShiftCompensation,initialRect:ae,measure:j.draggable.measure});let I=Er(P,j.draggable.measure,ae),oe=Er(P?P.parentElement:null),L=(0,e.useRef)({activatorEvent:null,active:null,activeNode:P,collisionRect:null,collisions:null,droppableRects:M,draggableNodes:b,draggingNode:null,draggingNodeRect:null,droppableContainers:S,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),R=S.getNodeFor(L.current.over?.id),se=Rr({measure:j.dragOverlay.measure}),ce=se.nodeRef.current??P,le=v?se.rect??I:null,ue=!!(se.nodeRef.current&&se.rect),de=Dr(ue?null:I),fe=Pr(ce?B(ce):null),pe=kr(v?R??P:null),me=Ir(pe),he=Xr(u,{transform:{x:x.x-de.x,y:x.y-de.y,scaleX:1,scaleY:1},activatorEvent:O,active:T,activeNodeRect:I,containerNodeRect:oe,draggingNodeRect:le,over:L.current.over,overlayNodeRect:se.rect,scrollableAncestors:pe,scrollableAncestorRects:me,windowRect:fe}),z=F?Lt(F,x):null,ge=Ar(pe),_e=jr(ge),ve=jr(ge,[I]),ye=Lt(he,_e),be=le?yn(le,he):null,xe=T&&be?c({active:T,collisionRect:be,droppableRects:M,droppableContainers:ne,pointerCoordinates:z}):null,Se=dn(xe,`id`),[Ce,we]=(0,e.useState)(null),Te=gn(ue?he:Lt(he,ve),Ce?.rect??null,I),Ee=(0,e.useRef)(null),De=(0,e.useCallback)((e,t)=>{let{sensor:r,options:i}=t;if(E.current==null)return;let a=b.get(E.current);if(!a)return;let o=e.nativeEvent;Ee.current=new r({active:E.current,activeNode:a,event:o,options:i,context:L,onAbort(e){if(!b.get(e))return;let{onDragAbort:t}=A.current,n={id:e};t?.(n),m({type:`onDragAbort`,event:n})},onPending(e,t,n,r){if(!b.get(e))return;let{onDragPending:i}=A.current,a={id:e,constraint:t,initialCoordinates:n,offset:r};i?.(a),m({type:`onDragPending`,event:a})},onStart(e){let t=E.current;if(t==null)return;let r=b.get(t);if(!r)return;let{onDragStart:i}=A.current,a={activatorEvent:o,active:{id:t,data:r.data,rect:w}};(0,n.unstable_batchedUpdates)(()=>{i?.(a),_(ei.Initializing),p({type:V.DragStart,initialCoordinates:e,active:t}),m({type:`onDragStart`,event:a}),D(Ee.current),k(o)})},onMove(e){p({type:V.DragMove,coordinates:e})},onEnd:s(V.DragEnd),onCancel:s(V.DragCancel)});function s(e){return async function(){let{active:t,collisions:r,over:i,scrollAdjustedTranslate:a}=L.current,s=null;if(t&&a){let{cancelDrop:n}=A.current;s={activatorEvent:o,active:t,collisions:r,delta:a,over:i},e===V.DragEnd&&typeof n==`function`&&await Promise.resolve(n(s))&&(e=V.DragCancel)}E.current=null,(0,n.unstable_batchedUpdates)(()=>{p({type:e}),_(ei.Uninitialized),we(null),D(null),k(null),Ee.current=null;let t=e===V.DragEnd?`onDragEnd`:`onDragCancel`;if(s){let e=A.current[t];e?.(s),m({type:t,event:s})}})}}},[b]),Oe=gr(s,(0,e.useCallback)((e,t)=>(n,r)=>{let i=n.nativeEvent,a=b.get(r);if(E.current!==null||!a||i.dndKit||i.defaultPrevented)return;let o={active:a};e(n,t.options,o)===!0&&(i.dndKit={capturedBy:t.sensor},E.current=r,De(n,t))},[b,De]));Mr(s),Dt(()=>{I&&g===ei.Initializing&&_(ei.Initialized)},[I,g]),(0,e.useEffect)(()=>{let{onDragMove:e}=A.current,{active:t,activatorEvent:r,collisions:i,over:a}=L.current;if(!t||!r)return;let o={active:t,activatorEvent:r,collisions:i,delta:{x:ye.x,y:ye.y},over:a};(0,n.unstable_batchedUpdates)(()=>{e?.(o),m({type:`onDragMove`,event:o})})},[ye.x,ye.y]),(0,e.useEffect)(()=>{let{active:e,activatorEvent:t,collisions:r,droppableContainers:i,scrollAdjustedTranslate:a}=L.current;if(!e||E.current==null||!t||!a)return;let{onDragOver:o}=A.current,s=i.get(Se),c=s&&s.rect.current?{id:s.id,rect:s.rect.current,data:s.data,disabled:s.disabled}:null,l={active:e,activatorEvent:t,collisions:r,delta:{x:a.x,y:a.y},over:c};(0,n.unstable_batchedUpdates)(()=>{we(c),o?.(l),m({type:`onDragOver`,event:l})})},[Se]),Dt(()=>{L.current={activatorEvent:O,active:T,activeNode:P,collisionRect:be,collisions:xe,droppableRects:M,draggableNodes:b,draggingNode:ce,draggingNodeRect:le,droppableContainers:S,over:Ce,scrollableAncestors:pe,scrollAdjustedTranslate:ye},w.current={initial:le,translated:be}},[T,P,xe,be,b,ce,le,M,S,Ce,pe,ye]),fr({...ie,delta:x,draggingRect:be,pointerCoordinates:z,scrollableAncestors:pe,scrollableAncestorRects:me});let ke=(0,e.useMemo)(()=>({active:T,activeNode:P,activeNodeRect:I,activatorEvent:O,collisions:xe,containerNodeRect:oe,dragOverlay:se,draggableNodes:b,droppableContainers:S,droppableRects:M,over:Ce,measureDroppableContainers:N,scrollableAncestors:pe,scrollableAncestorRects:me,measuringConfiguration:j,measuringScheduled:re,windowRect:fe}),[T,P,I,O,xe,oe,se,b,S,M,Ce,N,pe,me,j,re,fe]),Ae=(0,e.useMemo)(()=>({activatorEvent:O,activators:Oe,active:T,activeNodeRect:I,ariaDescribedById:{draggable:te},dispatch:p,draggableNodes:b,over:Ce,measureDroppableContainers:N}),[O,Oe,T,I,p,te,b,Ce,N]);return e.default.createElement(Xt.Provider,{value:h},e.default.createElement(Gr.Provider,{value:Ae},e.default.createElement(Kr.Provider,{value:ke},e.default.createElement($r.Provider,{value:Te},o)),e.default.createElement(Yr,{disabled:i?.restoreFocus===!1})),e.default.createElement(tn,{...i,hiddenTextDescribedById:te}));function je(){let e=ee?.autoScrollEnabled===!1,t=typeof a==`object`?a.enabled===!1:a===!1,n=v&&!e&&!t;return typeof a==`object`?{...a,enabled:n}:{enabled:n}}}),ni=(0,e.createContext)(null),ri=`button`,ii=`Draggable`;function ai(t){let{id:n,data:r,disabled:i=!1,attributes:a}=t,o=Ft(ii),{activators:s,activatorEvent:c,active:l,activeNodeRect:u,ariaDescribedById:d,draggableNodes:f,over:p}=(0,e.useContext)(Gr),{role:m=ri,roleDescription:h=`draggable`,tabIndex:g=0}=a??{},_=l?.id===n,v=(0,e.useContext)(_?$r:ni),[y,b]=Mt(),[x,S]=Mt(),C=Nr(s,n),w=At(r);return Dt(()=>(f.set(n,{id:n,key:o,node:y,activatorNode:x,data:w}),()=>{let e=f.get(n);e&&e.key===o&&f.delete(n)}),[f,n]),{active:l,activatorEvent:c,activeNodeRect:u,attributes:(0,e.useMemo)(()=>({role:m,tabIndex:g,"aria-disabled":i,"aria-pressed":_&&m===ri?!0:void 0,"aria-roledescription":h,"aria-describedby":d.draggable}),[i,m,g,_,h,d.draggable]),isDragging:_,listeners:i?void 0:C,node:y,over:p,setNodeRef:b,setActivatorNodeRef:S,transform:v}}function oi(){return(0,e.useContext)(Kr)}var si=`Droppable`,ci={timeout:25};function li(t){let{data:n,disabled:r=!1,id:i,resizeObserverConfig:a}=t,o=Ft(si),{active:s,dispatch:c,over:l,measureDroppableContainers:u}=(0,e.useContext)(Gr),d=(0,e.useRef)({disabled:r}),f=(0,e.useRef)(!1),p=(0,e.useRef)(null),m=(0,e.useRef)(null),{disabled:h,updateMeasurementsFor:g,timeout:_}={...ci,...a},v=At(g??i),y=wr({callback:(0,e.useCallback)(()=>{if(!f.current){f.current=!0;return}m.current!=null&&clearTimeout(m.current),m.current=setTimeout(()=>{u(Array.isArray(v.current)?v.current:[v.current]),m.current=null},_)},[_]),disabled:h||!s}),[b,x]=Mt((0,e.useCallback)((e,t)=>{y&&(t&&(y.unobserve(t),f.current=!1),e&&y.observe(e))},[y])),S=At(n);return(0,e.useEffect)(()=>{!y||!b.current||(y.disconnect(),f.current=!1,y.observe(b.current))},[b,y]),(0,e.useEffect)(()=>(c({type:V.RegisterDroppable,element:{id:i,key:o,disabled:r,node:b,rect:p,data:S}}),()=>c({type:V.UnregisterDroppable,key:o,id:i})),[i]),(0,e.useEffect)(()=>{r!==d.current.disabled&&(c({type:V.SetDroppableDisabled,id:i,key:o,disabled:r}),d.current.disabled=r)},[i,o,r,c]),{active:s,rect:p,isOver:l?.id===i,node:b,over:l,setNodeRef:x}}function ui(t){let{animation:n,children:r}=t,[i,a]=(0,e.useState)(null),[o,s]=(0,e.useState)(null),c=Nt(r);return!r&&!i&&c&&a(c),Dt(()=>{if(!o)return;let e=i?.key,t=i?.props.id;if(e==null||t==null){a(null);return}Promise.resolve(n(t,o)).then(()=>{a(null)})},[n,i,o]),e.default.createElement(e.default.Fragment,null,r,i?(0,e.cloneElement)(i,{ref:s}):null)}var di={x:0,y:0,scaleX:1,scaleY:1};function fi(t){let{children:n}=t;return e.default.createElement(Gr.Provider,{value:Wr},e.default.createElement($r.Provider,{value:di},n))}var pi={position:`fixed`,touchAction:`none`},mi=e=>Bt(e)?`transform 250ms ease`:void 0,hi=(0,e.forwardRef)((t,n)=>{let{as:r,activatorEvent:i,adjustScale:a,children:o,className:s,rect:c,style:l,transform:u,transition:d=mi}=t;if(!c)return null;let f=a?u:{...u,scaleX:1,scaleY:1},p={...pi,width:c.width,height:c.height,top:c.top,left:c.left,transform:Ut.Transform.toString(f),transformOrigin:a&&i?cn(i,c):void 0,transition:typeof d==`function`?d(i):d,...l};return e.default.createElement(r,{className:s,style:p,ref:n},o)}),gi={duration:250,easing:`ease`,keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:Ut.Transform.toString(t)},{transform:Ut.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:r}=t,i={},{styles:a,className:o}=e;if(a!=null&&a.active)for(let[e,t]of Object.entries(a.active))t!==void 0&&(i[e]=n.node.style.getPropertyValue(e),n.node.style.setProperty(e,t));if(a!=null&&a.dragOverlay)for(let[e,t]of Object.entries(a.dragOverlay))t!==void 0&&r.node.style.setProperty(e,t);return o!=null&&o.active&&n.node.classList.add(o.active),o!=null&&o.dragOverlay&&r.node.classList.add(o.dragOverlay),function(){for(let[e,t]of Object.entries(i))n.node.style.setProperty(e,t);o!=null&&o.active&&n.node.classList.remove(o.active)}})({styles:{active:{opacity:`0`}}})};function _i(e){let{config:t,draggableNodes:n,droppableContainers:r,measuringConfiguration:i}=e;return Ot((e,a)=>{if(t===null)return;let o=n.get(e);if(!o)return;let s=o.node.current;if(!s)return;let c=Lr(a);if(!c)return;let{transform:l}=B(a).getComputedStyle(a),u=bn(l);if(!u)return;let d=typeof t==`function`?t:vi(t);return Hn(s,i.draggable.measure),d({active:{id:e,data:o.data,node:s,rect:i.draggable.measure(s)},draggableNodes:n,dragOverlay:{node:a,rect:i.dragOverlay.measure(c)},droppableContainers:r,measuringConfiguration:i,transform:u})})}function vi(e){let{duration:t,easing:n,sideEffects:r,keyframes:i}={...gi,...e};return e=>{let{active:a,dragOverlay:o,transform:s,...c}=e;if(!t)return;let l={x:o.rect.left-a.rect.left,y:o.rect.top-a.rect.top},u={scaleX:s.scaleX===1?1:a.rect.width*s.scaleX/o.rect.width,scaleY:s.scaleY===1?1:a.rect.height*s.scaleY/o.rect.height},d={x:s.x-l.x,y:s.y-l.y,...u},f=i({...c,active:a,dragOverlay:o,transform:{initial:s,final:d}}),[p]=f,m=f[f.length-1];if(JSON.stringify(p)===JSON.stringify(m))return;let h=r?.({active:a,dragOverlay:o,...c}),g=o.node.animate(f,{duration:t,easing:n,fill:`forwards`});return new Promise(e=>{g.onfinish=()=>{h?.(),e()}})}}var yi=0;function bi(t){return(0,e.useMemo)(()=>{if(t!=null)return yi++,yi},[t])}var xi=e.default.memo(t=>{let{adjustScale:n=!1,children:r,dropAnimation:i,style:a,transition:o,modifiers:s,wrapperElement:c=`div`,className:l,zIndex:u=999}=t,{activatorEvent:d,active:f,activeNodeRect:p,containerNodeRect:m,draggableNodes:h,droppableContainers:g,dragOverlay:_,over:v,measuringConfiguration:y,scrollableAncestors:b,scrollableAncestorRects:x,windowRect:S}=oi(),C=(0,e.useContext)($r),w=bi(f?.id),T=Xr(s,{activatorEvent:d,active:f,activeNodeRect:p,containerNodeRect:m,draggingNodeRect:_.rect,over:v,overlayNodeRect:_.rect,scrollableAncestors:b,scrollableAncestorRects:x,transform:C,windowRect:S}),E=xr(p),ee=_i({config:i,draggableNodes:h,droppableContainers:g,measuringConfiguration:y}),D=E?_.setRef:void 0;return e.default.createElement(fi,null,e.default.createElement(ui,{animation:ee},f&&w?e.default.createElement(hi,{key:w,id:f.id,ref:D,as:c,activatorEvent:d,adjustScale:n,className:l,transition:o,rect:E,style:{zIndex:u,...a},transform:T},r):null))});function Si(e,t,n){let r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function Ci(e,t){return e.reduce((e,n,r)=>{let i=t.get(n);return i&&(e[r]=i),e},Array(e.length))}function wi(e){return e!==null&&e>=0}function Ti(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{let{rects:t,activeNodeRect:n,activeIndex:r,overIndex:i,index:a}=e,o=t[r]??n;if(!o)return null;let s=ki(t,a,r);if(a===r){let e=t[i];return e?{x:rr&&a<=i?{x:-o.width-s,y:0,...Di}:a=i?{x:o.width+s,y:0,...Di}:{x:0,y:0,...Di}};function ki(e,t,n){let r=e[t],i=e[t-1],a=e[t+1];return!r||!i&&!a?0:n{let{rects:t,activeIndex:n,overIndex:r,index:i}=e,a=Si(t,r,n),o=t[i],s=a[i];return!s||!o?null:{x:s.left-o.left,y:s.top-o.top,scaleX:s.width/o.width,scaleY:s.height/o.height}},ji={scaleX:1,scaleY:1},Mi=e=>{let{activeIndex:t,activeNodeRect:n,index:r,rects:i,overIndex:a}=e,o=i[t]??n;if(!o)return null;if(r===t){let e=i[a];return e?{x:0,y:tt&&r<=a?{x:0,y:-o.height-s,...ji}:r=a?{x:0,y:o.height+s,...ji}:{x:0,y:0,...ji}};function Ni(e,t,n){let r=e[t],i=e[t-1],a=e[t+1];return r?ni.map(e=>typeof e==`object`&&`id`in e?e.id:e),[i]),h=s!=null,g=s?m.indexOf(s.id):-1,_=u?m.indexOf(u.id):-1,v=(0,e.useRef)(m),y=!Ti(m,v.current),b=_!==-1&&g===-1||y,x=Ei(o);Dt(()=>{y&&h&&d(m)},[y,m,h,d]),(0,e.useEffect)(()=>{v.current=m},[m]);let S=(0,e.useMemo)(()=>({activeIndex:g,containerId:f,disabled:x,disableTransforms:b,items:m,overIndex:_,useDragOverlay:p,sortedRects:Ci(m,l),strategy:a}),[g,f,x.draggable,x.droppable,b,m,_,l,p,a]);return e.default.createElement(Fi.Provider,{value:S},n)}var Li=e=>{let{id:t,items:n,activeIndex:r,overIndex:i}=e;return Si(n,r,i).indexOf(t)},Ri=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:i,items:a,newIndex:o,previousItems:s,previousContainerId:c,transition:l}=e;return!l||!r||s!==a&&i===o?!1:n?!0:o!==i&&t===c},zi={duration:200,easing:`ease`},Bi=`transform`,Vi=Ut.Transition.toString({property:Bi,duration:0,easing:`linear`}),Hi={roleDescription:`sortable`};function Ui(t){let{disabled:n,index:r,node:i,rect:a}=t,[o,s]=(0,e.useState)(null),c=(0,e.useRef)(r);return Dt(()=>{if(!n&&r!==c.current&&i.current){let e=a.current;if(e){let t=Cn(i.current,{ignoreTransform:!0}),n={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(n.x||n.y)&&s(n)}}r!==c.current&&(c.current=r)},[n,r,i,a]),(0,e.useEffect)(()=>{o&&s(null)},[o]),o}function Wi(t){let{animateLayoutChanges:n=Ri,attributes:r,disabled:i,data:a,getNewIndex:o=Li,id:s,strategy:c,resizeObserverConfig:l,transition:u=zi}=t,{items:d,containerId:f,activeIndex:p,disabled:m,disableTransforms:h,sortedRects:g,overIndex:_,useDragOverlay:v,strategy:y}=(0,e.useContext)(Fi),b=Gi(i,m),x=d.indexOf(s),S=(0,e.useMemo)(()=>({sortable:{containerId:f,index:x,items:d},...a}),[f,a,x,d]),C=(0,e.useMemo)(()=>d.slice(d.indexOf(s)),[d,s]),{rect:w,node:T,isOver:E,setNodeRef:ee}=li({id:s,data:S,disabled:b.droppable,resizeObserverConfig:{updateMeasurementsFor:C,...l}}),{active:D,activatorEvent:O,activeNodeRect:k,attributes:A,setNodeRef:te,listeners:ne,isDragging:j,over:M,setActivatorNodeRef:N,transform:re}=ai({id:s,data:S,attributes:{...Hi,...r},disabled:b.draggable}),P=yt(ee,te),F=!!D,ie=F&&!h&&wi(p)&&wi(_),ae=!v&&j,I=ie?(ae&&ie?re:null)??(c??y)({rects:g,activeNodeRect:k,activeIndex:p,overIndex:_,index:x}):null,oe=wi(p)&&wi(_)?o({id:s,items:d,activeIndex:p,overIndex:_}):x,L=D?.id,R=(0,e.useRef)({activeId:L,items:d,newIndex:oe,containerId:f}),se=d!==R.current.items,ce=n({active:D,containerId:f,isDragging:j,isSorting:F,id:s,index:x,items:d,newIndex:R.current.newIndex,previousItems:R.current.items,previousContainerId:R.current.containerId,transition:u,wasDragging:R.current.activeId!=null}),le=Ui({disabled:!ce,index:x,node:T,rect:w});return(0,e.useEffect)(()=>{F&&R.current.newIndex!==oe&&(R.current.newIndex=oe),f!==R.current.containerId&&(R.current.containerId=f),d!==R.current.items&&(R.current.items=d)},[F,oe,f,d]),(0,e.useEffect)(()=>{if(L===R.current.activeId)return;if(L!=null&&R.current.activeId==null){R.current.activeId=L;return}let e=setTimeout(()=>{R.current.activeId=L},50);return()=>clearTimeout(e)},[L]),{active:D,activeIndex:p,attributes:A,data:S,rect:w,index:x,newIndex:oe,items:d,isOver:E,isSorting:F,isDragging:j,listeners:ne,node:T,overIndex:_,over:M,setNodeRef:P,setActivatorNodeRef:N,setDroppableNodeRef:ee,setDraggableNodeRef:te,transform:le??I,transition:ue()};function ue(){if(le||se&&R.current.newIndex===x)return Vi;if(!(ae&&!Bt(O)||!u)&&(F||ce))return Ut.Transition.toString({...u,property:Bi})}}function Gi(e,t){return typeof e==`boolean`?{draggable:e,droppable:!1}:{draggable:e?.draggable??t.draggable,droppable:e?.droppable??t.droppable}}U.Down,U.Right,U.Up,U.Left;function Ki(e){return`containerType`in e}function qi(e){return`containerType`in e&&e.containerType===`section`}var Ji=[`page`,`section`,`row`,`column`,`element`],Yi=`-`;function Xi(e){return Ji.includes(e)}function Zi(e,t){return typeof e==`string`?`${e}${Yi}${t}`:`${e.type}${Yi}${e.id}`}function Qi(e){let t=e.indexOf(Yi),n=e.slice(0,t);if(!Xi(n))return null;let r=Number(e.slice(t+1));return!Number.isInteger(r)||r<=0?null:{type:n,id:r}}function $i(e,t){return e.type===t.type&&e.id===t.id}function ea(e,t){if(typeof e!=`object`||!e)throw TypeError(`${t}: expected NodeRef object, got ${typeof e}`);let n=e,r=n.type,i=n.id;if(typeof r!=`string`||!Xi(r))throw TypeError(`${t}: invalid NodeRef.type ${JSON.stringify(r)}`);if(typeof i!=`number`||!Number.isInteger(i)||i<=0)throw TypeError(`${t}: invalid NodeRef.id ${JSON.stringify(i)}`);return{type:r,id:i}}var W={toKey:Zi,fromKey:Qi,equals:$i,assert:ea},ta=class extends Error{status;statusText;constructor(e,t){super(`API error ${e}: ${t}`),this.name=`ApiError`,this.status=e,this.statusText=t}},na=class extends Error{constructor(e){super(e),this.name=`ConfigError`}},ra=`WeDevelop\\Grid\\Controllers\\GridController`;function ia(){let e=window.ss?.config;if(e===void 0)throw new na(`SilverStripe config is not available. Ensure the admin bundle is loaded before the grid editor.`);return e}function aa(){return ia().SecurityID}function oa(){let e=ia().sections.find(e=>e.name===ra);if(e===void 0)throw new na(`Controller section "${ra}" not found in CMS config. Ensure the grid module is installed.`);return e}function G(){return oa().controllerLink.replace(/\/+$/,``)}function sa(){let e=oa().gridAdapter;if(e===void 0)throw new na(`Grid adapter configuration is missing. Ensure the grid module is installed and configured.`);return e}async function ca(e){try{let t=await e.json();if(typeof t==`object`&&t){let e=t;if(typeof e.message==`string`&&e.message!==``)return e.message;if(typeof e.errorMessage==`string`&&e.errorMessage!==``)return e.errorMessage}}catch{}return e.statusText}async function la(e){let t=await fetch(e,{credentials:`same-origin`,headers:{Accept:`application/json`}});if(!t.ok){let e=await ca(t);throw new ta(t.status,e)}return t.json()}async function ua(e,t,n){let r=await fetch(t,{method:e,credentials:`same-origin`,headers:{"Content-Type":`application/json`,Accept:`application/json`,"X-SecurityID":aa()},body:JSON.stringify(n)});if(!r.ok){let e=await ca(r);throw new ta(r.status,e)}}async function da(e,t){return ua(`POST`,e,t)}async function fa(e,t){return ua(`PATCH`,e,t)}function pa(e){if(e===void 0)return``;let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&t.append(n,String(r));let n=t.toString();return n===``?``:`?${n}`}async function ma(e,t){let n=`${e}${pa(t)}`,r=await fetch(n,{method:`DELETE`,credentials:`same-origin`,headers:{Accept:`application/json`,"X-SecurityID":aa()}});if(!r.ok){let e=await ca(r);throw new ta(r.status,e)}}async function ha(e,t,n){let r=G(),i=encodeURIComponent(t);return ga(await la(n===void 0?`${r}/api/readTree/${e}/${i}`:`${r}/api/readTree/${e}/${i}/version/${n}`))}function ga(e){if(typeof e!=`object`||!e)throw TypeError(`tree response: expected an object`);let t=e,n=W.assert(t.rootParent,`tree response.rootParent`);if(!Array.isArray(t.nodes))throw TypeError("tree response: expected `nodes` to be an array");let r={},i=t.overrideCounts;if(typeof i==`object`&&i)for(let[e,t]of Object.entries(i))typeof t==`number`&&(r[e]=t);return{rootParent:n,nodes:t.nodes.map(e=>va(e)),overrideCounts:r}}function _a(e){return e}function va(e){if(typeof e!=`object`||!e)throw TypeError(`tree node: expected an object`);let t=e,n=W.assert(t.self,`tree node.self`),r=W.assert(t.parent,`tree node.parent`),i=_a({...t,self:n,parent:r,nodeKey:W.toKey(n.type,n.id),parentKey:W.toKey(r.type,r.id),id:n.id});return Ki(i)&&Array.isArray(t.children)&&(i.children=t.children.map(e=>va(e))),i}async function ya(e){await da(`${G()}/api/create`,e)}async function ba(e){await ma(`${G()}/api/delete`,{id:e})}async function xa(e){await da(`${G()}/api/duplicate`,{id:e})}async function Sa(e){await fa(`${G()}/api/reorder`,e)}async function Ca(e){await da(`${G()}/api/createContent`,e)}async function wa(e){await fa(`${G()}/api/updateGridSettings`,e)}async function Ta(e){await ma(`${G()}/api/resetGridSettingsOverrides`,{pageId:e.pageId,zone:e.zone,viewport:e.viewport})}async function Ea(e){await da(`${G()}/api/duplicateTo`,e)}async function Da(e,t,n){return la(`${G()}/api/acceptableContainers/${e}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`)}async function Oa(e){return la(`${G()}/api/zones/${e}`)}async function ka(e){return la(`${G()}/api/pages${e?`?search=${encodeURIComponent(e)}`:``}`)}var Aa={elementTree:{all:()=>[`elementTree`],byPage:(e,t,n)=>n===void 0?[`elementTree`,e,t]:[`elementTree`,e,t,n]},pages:{all:()=>[`pages`],search:e=>[`pages`,e]},zones:{byPage:e=>[`zones`,e]},acceptableContainers:{byTarget:(e,t,n)=>[`acceptableContainers`,e,t,n]}};function ja(e,t,n){return{queryKey:e===null?[`elementTree`,`disabled`]:Aa.elementTree.byPage(e,t,n),queryFn:()=>{if(e===null)throw Error(`pageId is required — query should be disabled`);return ha(e,t,n)},enabled:e!==null}}function Ma(e,t,n){return at({...ja(e,t,n)})}function Na(e,t,n){let{data:r}=at({...ja(e,t,n),select:e=>e.overrideCounts});return r??{}}function Pa(e){return e!==`page`}function Fa(e,t){return W.toKey(e,t)}function Ia(e){let t=W.fromKey(e);return t===null||!Pa(t.type)?null:{type:t.type,id:t.id,key:e}}function La(e){return Ia(e)?.type??null}var Ra={section:`page`,row:`section`,column:`row`,element:`column`};function za(e,t,n){for(let r of e)t.set(r.nodeKey,r),Ki(r)&&r.children&&(n.set(r.nodeKey,r.children),za(r.children,t,n))}function Ba(e){let t=new Map,n=new Map,r=W.toKey(e.rootParent);return n.set(r,e.nodes),za(e.nodes,t,n),{nodeMap:t,childrenByParentKey:n}}function Va(t){return(0,e.useMemo)(()=>Ba(t),[t])}function Ha(e,t,n,r){let i=Ba(e),a=i.nodeMap.get(t);if(!a)return e;let o=a.parentKey,s=i.childrenByParentKey.get(o);if(!s)return e;let c=s.findIndex(e=>e.nodeKey===t);if(c===-1||Ua(o,c,s,n,r))return e;let l={rootParent:e.rootParent,nodes:structuredClone(e.nodes),overrideCounts:{...e.overrideCounts}},u=Ba(l),d=u.childrenByParentKey.get(o);if(!d)return e;let f=d.findIndex(e=>e.nodeKey===t);if(f===-1)return e;let[p]=d.splice(f,1),m=W.fromKey(n);if(m===null)return e;let h={...p,parent:m,parentKey:n},g=u.childrenByParentKey.get(n);return g?(Wa(g,h,r),l):e}function Ua(e,t,n,r,i){if(e!==r)return!1;if(i===null)return t===0;let a=n.findIndex(e=>e.nodeKey===i);return a===-1?!1:a+1===t}function Wa(e,t,n){if(n===null){e.unshift(t);return}let r=e.findIndex(e=>e.nodeKey===n);if(r===-1){e.push(t);return}e.splice(r+1,0,t)}function Ga(){let[t,n]=(0,e.useState)(null),r=(0,e.useRef)(null),i=(0,e.useRef)(null),a=(0,e.useRef)(!1),o=(0,e.useRef)(null),s=(0,e.useRef)(null),c=(0,e.useRef)(null);return{pendingTree:t,collisionRefs:(0,e.useMemo)(()=>({hasPendingMoveRef:a,pendingContainerItemsRef:s,sourceContainerItemsRef:c,overRectRef:o}),[]),applyPendingMove:(0,e.useCallback)((e,t,o,c)=>{let l=Ha(c,W.toKey(e.type,e.id),t,o===null?null:W.toKey(e.type,o));if(l===c)return null;let u=Ba(l);r.current=l,i.current=u,a.current=!0;let d=u.childrenByParentKey.get(t)??[];return s.current=new Set(d.map(t=>Fa(e.type,t.self.id))),n(l),{tree:l,maps:u}},[]),setSourceSiblings:(0,e.useCallback)(e=>{c.current=e},[]),getEffective:(0,e.useCallback)((e,t)=>r.current!==null&&i.current!==null?{tree:r.current,maps:i.current}:{tree:e,maps:t},[]),clear:(0,e.useCallback)(()=>{r.current=null,i.current=null,a.current=!1,o.current=null,s.current=null,c.current=null,n(null)},[])}}function Ka(e,t,n){let r=n===`column`;return(r?e.x:e.y)<(r?t.left+t.width/2:t.top+t.height/2)?`before`:`after`}function qa(e){let t=Ia(e.activeId);if(!t||e.sourceParentKey===e.targetParentKey&&e.sourceIndex===e.overIndex)return null;let n=Ja(e),r=n===null?null:e.maps.nodeMap.get(n)??null;return{element:{type:t.type,id:t.id},parent:e.targetParent,after:r?r.self:null}}function Ja(e){for(let t=e.overIndex-1;t>=0;t--){let n=e.containerItems[t];if(n!==e.activeId)return n}return null}function Ya(e){let{activeParsed:t,overParsed:n,pointer:r,maps:i,sourceParentKey:a,sourceIndex:o,overRect:s}=e,c=W.toKey(t.type,t.id);if(n.type===t.type){let e=W.toKey(n.type,n.id),l=i.nodeMap.get(e);if(!l)return null;let u=l.parent,d=l.parentKey,f=(i.childrenByParentKey.get(d)??[]).map(e=>e.nodeKey),p=f.filter(e=>e!==c),m;if(a===d){let t=f.indexOf(e);m=t===-1?p.length:t,p.splice(m,0,c)}else{let n=p.indexOf(e);n===-1?m=p.length:(m=n,r!==null&&Ka(r,s,t.type)===`after`&&(m+=1));let i=Math.min(m,p.length);p.splice(i,0,c)}return qa({activeId:c,targetParent:u,targetParentKey:d,overIndex:p.indexOf(c),containerItems:p,sourceParentKey:a,sourceIndex:o,maps:i})}let l=W.toKey(n.type,n.id),u=i.nodeMap.get(l);if(!u||!Ki(u))return null;let d=u.self,f=W.toKey(d),p=(u.children??[]).map(e=>e.nodeKey).filter(e=>e!==c);return p.push(c),qa({activeId:c,targetParent:d,targetParentKey:f,overIndex:p.indexOf(c),containerItems:p,sourceParentKey:a,sourceIndex:o,maps:i})}var Xa=e=>{let{collisionRect:t,droppableContainers:n}=e,r=t.left+t.width/2,i=t.top+t.height/2,a=[];for(let e of n){let t=e.node.current;if(!t)continue;let n=t.getBoundingClientRect(),o=n.left+n.width/2,s=n.top+n.height/2,c=r-o,l=i-s;a.push({id:e.id,data:{droppableContainer:e,value:c*c+l*l}})}return a.sort((e,t)=>e.data?.value-t.data?.value)};function Za(e,t){let n=La(e);return n===null?[]:t.filter(e=>La(String(e.id))===n)}function Qa(e,t){let n=La(e);if(n===null)return[];let r=Ra[n];return t.filter(e=>{let t=La(String(e.id));return r===`page`?t===null:t===r})}var $a=e=>{let{active:t,collisionRect:n,droppableContainers:r,droppableRects:i,pointerCoordinates:a}=e,o=t.rect.current.initial;if(o===null)return[];let s=o.left+o.width/2,c=o.top+o.height/2,l=n.left+n.width/2,u=n.top+n.height/2,d=a?.x??l,f=a?.y??u,p=[];for(let e of r){let t=i.get(e.id);if(t===void 0)continue;let r=t.left+t.width/2,a=t.top+t.height/2,o=co&&g<=o||c=o,v=s>m&&h<=m||s=m,y=d>t.left-50&&dt.top-150&&fe.data?.value-t.data?.value)};function eo(e){let t=!1,n;return r=>{let i=String(r.active.id),a=e.sourceContainerItemsRef?.current;a!==n&&(t=!1,n=a);let o=r.droppableContainers.filter(e=>e.id!==r.active.id),s=t=>{if(e.overRectRef&&t.length>0){let n=t[0].id,i=r.droppableContainers.find(e=>e.id===n);i?.node.current&&(e.overRectRef.current={id:n,nodeRef:i.node})}return t},c=Za(i,o);if(e.hasPendingMoveRef.current){let t=e.pendingContainerItemsRef?.current,n=t==null?c:c.filter(e=>t.has(e.id)),i=Xa({...r,droppableContainers:n});if(i.length>0)return i}else{let n=e.sourceContainerItemsRef?.current,i=n&&n.size>0?c.filter(e=>n.has(e.id)):c,a=$a({...r,droppableContainers:i});if(a.length>0)return t=!0,s(a);let{pointerCoordinates:o,droppableRects:l}=r;if(o&&n&&n.size>0&&i.some(e=>{let t=l.get(e.id);return t===void 0?!1:o.x>=t.left&&o.x<=t.left+t.width&&o.y>=t.top&&o.y<=t.top+t.height})){if(t){let e=Xa({...r,droppableContainers:i});if(e.length>0)return s(e)}return[]}}let l=Qa(i,o);if(r.pointerCoordinates){let e=r.pointerCoordinates,t=l.find(t=>{let n=r.droppableRects.get(t.id);return n===void 0?!1:e.x>=n.left&&e.x<=n.left+n.width&&e.y>=n.top&&e.y<=n.top+n.height});if(t)return s([{id:t.id,data:{droppableContainer:t,value:0}}])}return s(pn({...r,droppableContainers:l}))}}var to=(0,e.createContext)({activeType:null});function no(){return(0,e.useContext)(to)}function ro(e){let t=e.activatorEvent;if(!(t instanceof PointerEvent))return null;let n=e.active.rect.current.initial,r=e.active.rect.current.translated;return!n||!r?null:{x:r.left+(t.clientX-n.left),y:r.top+(t.clientY-n.top)}}var io=8;function ao({tree:t,onReorder:n}){let[r,i]=(0,e.useState)(null),a=Va(t),o=Ga(),[s]=(0,e.useState)(()=>eo(o.collisionRefs)),c=an(rn(ir,{activationConstraint:{distance:io}})),l=(0,e.useCallback)(e=>{let t=String(e.active.id),n=Ia(t);if(!n)return;let r=a.nodeMap.get(n.key);if(!r)return;let s=(a.childrenByParentKey.get(r.parentKey)??[]).filter(e=>e.nodeKey!==n.key);o.setSourceSiblings(new Set(s.map(e=>Fa(n.type,e.self.id)))),i({activeId:t,activeType:n.type,activeNode:r})},[a,o]),u=(0,e.useCallback)(e=>{let{active:n,over:r}=e;if(!r||n.id===r.id)return;let i=Ia(String(n.id)),s=Ia(String(r.id));if(!i||!s)return;let{tree:c,maps:l}=o.getEffective(t,a),u=l.nodeMap.get(i.key);if(!u)return;let d,f;if(s.type===i.type){let t=l.nodeMap.get(s.key);if(!t)return;d=t.parent;let n=ro(e);if(n!==null&&Ka(n,r.rect,i.type)===`before`){let e=l.childrenByParentKey.get(t.parentKey)??[],n=e.findIndex(e=>e.nodeKey===s.key);f=n>0?e[n-1].self:null}else f=t.self}else{let e=l.nodeMap.get(s.key);if(!e||!Ki(e))return;d=e.self;let t=e.children??[];f=t.length>0?t[t.length-1].self:null}if(W.equals(u.parent,d))return;let p=W.toKey(d.type,d.id);o.applyPendingMove(i,p,f?.id??null,c)},[t,a,o]),d=(0,e.useCallback)(e=>{i(null);let{active:r,over:s}=e;if(!s){o.clear();return}if(r.id===s.id){o.clear();return}let c=Ia(String(r.id)),l=Ia(String(s.id));if(!c||!l){o.clear();return}let u=a.nodeMap.get(c.key);if(!u){o.clear();return}let d=u.parentKey,f=a.childrenByParentKey.get(d);if(!f){o.clear();return}let p=f.findIndex(e=>e.nodeKey===c.key),{maps:m}=o.getEffective(t,a),h=o.collisionRefs.overRectRef.current,g=h?.nodeRef.current,_=String(h?.id)===String(s.id)&&g?g.getBoundingClientRect():s.rect,v=Ya({activeParsed:c,overParsed:l,pointer:ro(e),maps:m,sourceParentKey:d,sourceIndex:p,overRect:_});v?n(v.element,v.parent,v.after,o.clear):o.clear()},[t,a,n,o]),f=(0,e.useCallback)(()=>{i(null),o.clear()},[o]);return{dndContextProps:(0,e.useMemo)(()=>({sensors:c,collisionDetection:s,onDragStart:l,onDragOver:u,onDragEnd:d,onDragCancel:f}),[c,s,l,u,d,f]),dragState:r,pendingTree:o.pendingTree}}function oo(){let e=window.jQuery?.(`.cms-edit-form`);!e||e.length===0||e.trigger(`aftersubmitform`,{xhr:{getResponseHeader:()=>null}})}function so(e,t){let n=We();return{onSuccess:()=>{n.invalidateQueries({queryKey:Aa.elementTree.byPage(e,t)}),oo()},onError:e=>{p(e.message)}}}function co(e,t){return ot({mutationFn:ya,...so(e,t)})}function lo(e,t){return ot({mutationFn:Ca,...so(e,t)})}function uo(e,t){return ot({mutationFn:ba,...so(e,t)})}function fo(e,t){return ot({mutationFn:xa,...so(e,t)})}function po(e,t){return ot({mutationFn:Ea,...so(e,t)})}function mo(e,t){return ot({mutationFn:wa,...so(e,t)})}function ho(e,t){return ot({mutationFn:Ta,...so(e,t)})}function go(e,t){let n=We(),r=Aa.elementTree.byPage(e,t);return ot({mutationFn:({params:e})=>Sa(e),onMutate:async({params:e,tree:t,clearPendingTree:i})=>{await n.cancelQueries({queryKey:r});let a=n.getQueryData(r),o=Ha(t,W.toKey(e.element),W.toKey(e.parent),e.after===null?null:W.toKey(e.after));return n.setQueryData(r,o),i?.(),a},onError:(e,{clearPendingTree:t},i)=>{t?.(),i!==void 0&&n.setQueryData(r,i),p(e.message)},onSuccess:async()=>{await n.invalidateQueries({queryKey:r}),oo()}})}function _o(e){return`grid:collapsed:${String(e)}`}function vo(e){try{let t=localStorage.getItem(_o(e));if(t===null)return new Set;let n=JSON.parse(t);if(!Array.isArray(n))return new Set;let r=n.filter(e=>typeof e==`string`&&W.fromKey(e)!==null);return new Set(r)}catch{return new Set}}function yo(e,t){try{localStorage.setItem(_o(e),JSON.stringify([...t]))}catch{}}function bo(t){let[n,r]=(0,e.useState)(()=>vo(t)),i=(0,e.useCallback)(e=>n.has(e),[n]),a=(0,e.useCallback)(e=>{r(n=>{let r=new Set(n);return r.has(e)?r.delete(e):r.add(e),yo(t,r),r})},[t]);return(0,e.useMemo)(()=>({isCollapsed:i,toggle:a}),[i,a])}var xo=(0,e.createContext)(null);function So(){let t=(0,e.useContext)(xo);if(t===null)throw Error(`useCollapse must be used within a `);return t}var Co=null,wo=null;function To(){return Co===null&&(Co=sa()),Co}function Eo(){return To().viewports}function Do(){return To().defaultViewport}function Oo(){return To().columnCount}function ko(){return To().offsetStrategy}function Ao(){if(wo===null){let e=To().columnCount,t=[];for(let n=1;n<=e;n++)t.push({value:n,label:`${n}/${e}`});t.push({value:`hidden`,label:`hidden`}),wo=t}return wo}function jo(e){let t=To().columnCount,n=e===void 0?t-1:t-e,r=[];for(let e=0;e<=n;e++)r.push({value:e,label:e===0?`none`:`+${e}`});return r}function Mo(e,t){return e.overrides[t]??e.default}var No=(0,e.createContext)(null);function Po({initialViewport:t,children:n}){let[r,i]=(0,e.useState)(()=>t??Do()),a=(0,e.useMemo)(()=>({activeViewport:r,setActiveViewport:i}),[r]);return(0,v.jsx)(No.Provider,{value:a,children:n})}function Fo(){let t=(0,e.useContext)(No);if(t===null)throw Error(`useViewportContext must be used within a ViewportProvider`);return t}var Io=(0,e.createContext)(null);function Lo({value:e,children:t}){return(0,v.jsx)(Io.Provider,{value:e,children:t})}function Ro(){let t=(0,e.useContext)(Io);if(t===null)throw Error(`useGridEditorContext must be used within a GridEditorProvider`);return t}var zo=(0,e.createContext)(!1);function Bo({value:e,children:t}){return(0,v.jsx)(zo.Provider,{value:e,children:t})}function Vo(){return(0,e.useContext)(zo)}function Ho(e){let t=new Map,n=new Map,r=e=>{if(t.set(e.nodeKey,e),n.set(e.id,e),Ki(e)&&e.children!==null)for(let t of e.children)r(t)};for(let t of e.nodes)r(t);return{byKey:t,byId:n}}function Uo(e){return{id:e.id,title:e.title,type:e.self.type}}function Wo(e,t){let{byKey:n,byId:r}=Ho(e),i=r.get(t);if(i===void 0)return[];let a=[],o=i;for(;;){let e=n.get(o.parentKey);if(e===void 0)break;a.push(e.id),o=e}return a.reverse()}function Go(e,t){let{byKey:n,byId:r}=Ho(e),i=r.get(t);if(i===void 0)return[];let a=[Uo(i)],o=i;for(;;){let e=n.get(o.parentKey);if(e===void 0)break;a.push(Uo(e)),o=e}return a.reverse()}var Ko=`grid:inspect-mode`;function qo(){try{return localStorage.getItem(Ko)===`true`}catch{return!1}}function Jo(e){try{localStorage.setItem(Ko,e?`true`:`false`)}catch{}}var Yo=(0,e.createContext)(null),Xo=new Set;function Zo(e){for(let t of Xo)t(e)}function Qo({children:t}){let[n,r]=(0,e.useState)(()=>qo()),[i,a]=(0,e.useState)(null),[o,s]=(0,e.useState)(!1);(0,e.useEffect)(()=>{let e=e=>r(e);Xo.add(e);let t=e=>{e.key===`grid:inspect-mode`&&r(qo())};return window.addEventListener(`storage`,t),()=>{Xo.delete(e),window.removeEventListener(`storage`,t)}},[]);let c=(0,e.useCallback)(e=>{Jo(e),r(e),Zo(e)},[]),l=(0,e.useCallback)(e=>{if(e===null){a(null),s(!1);return}a({source:`editor`,nodeKey:e.nodeKey,id:e.id,ancestorIds:[]}),s(!1)},[]),u=(0,e.useCallback)((e,t,n)=>{a({source:`preview`,id:e,ancestorIds:t,path:n}),s(!1)},[]),d=(0,e.useCallback)(()=>{a(null),s(!1)},[]),f=(0,e.useCallback)(e=>{s(e)},[]),p=(0,e.useMemo)(()=>({enabled:n,hover:i,missing:o,setEnabled:c,setEditorHover:l,setPreviewHover:u,clearHover:d,setMissing:f}),[n,i,o,c,l,u,d,f]);return(0,v.jsx)(Yo.Provider,{value:p,children:t})}function $o(){let t=(0,e.useContext)(Yo);if(t===null)throw Error(`useInspect must be used within `);return t}Object.freeze({status:`aborted`});function K(e,t,n){function r(n,r){if(n._zod||Object.defineProperty(n,`_zod`,{value:{def:r,constr:o,traits:new Set},enumerable:!1}),n._zod.traits.has(e))return;n._zod.traits.add(e),t(n,r);let i=o.prototype,a=Object.keys(i);for(let e=0;en?.Parent&&t instanceof n.Parent?!0:t?._zod?.traits?.has(e)}),Object.defineProperty(o,`name`,{value:e}),o}var es=class extends Error{constructor(){super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`)}},ts=class extends Error{constructor(e){super(`Encountered unidirectional transform during encode: ${e}`),this.name=`ZodEncodeError`}},ns={};function rs(e){return e&&Object.assign(ns,e),ns}function is(e){let t=Object.values(e).filter(e=>typeof e==`number`);return Object.entries(e).filter(([e,n])=>t.indexOf(+e)===-1).map(([e,t])=>t)}function as(e,t){return typeof t==`bigint`?t.toString():t}function os(e){return{get value(){{let t=e();return Object.defineProperty(this,`value`,{value:t}),t}throw Error(`cached value already set`)}}}function ss(e){return e==null}function cs(e){let t=+!!e.startsWith(`^`),n=e.endsWith(`$`)?e.length-1:e.length;return e.slice(t,n)}function ls(e,t){let n=(e.toString().split(`.`)[1]||``).length,r=t.toString(),i=(r.split(`.`)[1]||``).length;if(i===0&&/\d?e-\d?/.test(r)){let e=r.match(/\d?e-(\d?)/);e?.[1]&&(i=Number.parseInt(e[1]))}let a=n>i?n:i;return Number.parseInt(e.toFixed(a).replace(`.`,``))%Number.parseInt(t.toFixed(a).replace(`.`,``))/10**a}var us=Symbol(`evaluating`);function q(e,t,n){let r;Object.defineProperty(e,t,{get(){if(r!==us)return r===void 0&&(r=us,r=n()),r},set(n){Object.defineProperty(e,t,{value:n})},configurable:!0})}function ds(e,t,n){Object.defineProperty(e,t,{value:n,writable:!0,enumerable:!0,configurable:!0})}function fs(...e){let t={};for(let n of e)Object.assign(t,Object.getOwnPropertyDescriptors(n));return Object.defineProperties({},t)}function ps(e){return JSON.stringify(e)}var ms=`captureStackTrace`in Error?Error.captureStackTrace:(...e)=>{};function hs(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var gs=os(()=>{if(typeof navigator<`u`&&navigator?.userAgent?.includes(`Cloudflare`))return!1;try{return Function(``),!0}catch{return!1}});function _s(e){if(hs(e)===!1)return!1;let t=e.constructor;if(t===void 0||typeof t!=`function`)return!0;let n=t.prototype;return!(hs(n)===!1||Object.prototype.hasOwnProperty.call(n,`isPrototypeOf`)===!1)}function vs(e){return _s(e)?{...e}:Array.isArray(e)?[...e]:e}var ys=new Set([`string`,`number`,`symbol`]);function bs(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function xs(e,t,n){let r=new e._zod.constr(t??e._zod.def);return(!t||n?.parent)&&(r._zod.parent=e),r}function J(e){let t=e;if(!t)return{};if(typeof t==`string`)return{error:()=>t};if(t?.message!==void 0){if(t?.error!==void 0)throw Error("Cannot specify both `message` and `error` params");t.error=t.message}return delete t.message,typeof t.error==`string`?{...t,error:()=>t.error}:t}function Ss(e){return Object.keys(e).filter(t=>e[t]._zod.optin===`optional`&&e[t]._zod.optout===`optional`)}var Cs={safeint:[-(2**53-1),2**53-1],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]};function ws(e,t){let n=e._zod.def,r=n.checks;if(r&&r.length>0)throw Error(`.pick() cannot be used on object schemas containing refinements`);return xs(e,fs(e._zod.def,{get shape(){let e={};for(let r in t){if(!(r in n.shape))throw Error(`Unrecognized key: "${r}"`);t[r]&&(e[r]=n.shape[r])}return ds(this,`shape`,e),e},checks:[]}))}function Ts(e,t){let n=e._zod.def,r=n.checks;if(r&&r.length>0)throw Error(`.omit() cannot be used on object schemas containing refinements`);return xs(e,fs(e._zod.def,{get shape(){let r={...e._zod.def.shape};for(let e in t){if(!(e in n.shape))throw Error(`Unrecognized key: "${e}"`);t[e]&&delete r[e]}return ds(this,`shape`,r),r},checks:[]}))}function Es(e,t){if(!_s(t))throw Error(`Invalid input to extend: expected a plain object`);let n=e._zod.def.checks;if(n&&n.length>0){let n=e._zod.def.shape;for(let e in t)if(Object.getOwnPropertyDescriptor(n,e)!==void 0)throw Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.")}return xs(e,fs(e._zod.def,{get shape(){let n={...e._zod.def.shape,...t};return ds(this,`shape`,n),n}}))}function Ds(e,t){if(!_s(t))throw Error(`Invalid input to safeExtend: expected a plain object`);return xs(e,fs(e._zod.def,{get shape(){let n={...e._zod.def.shape,...t};return ds(this,`shape`,n),n}}))}function Os(e,t){return xs(e,fs(e._zod.def,{get shape(){let n={...e._zod.def.shape,...t._zod.def.shape};return ds(this,`shape`,n),n},get catchall(){return t._zod.def.catchall},checks:[]}))}function ks(e,t,n){let r=t._zod.def.checks;if(r&&r.length>0)throw Error(`.partial() cannot be used on object schemas containing refinements`);return xs(t,fs(t._zod.def,{get shape(){let r=t._zod.def.shape,i={...r};if(n)for(let t in n){if(!(t in r))throw Error(`Unrecognized key: "${t}"`);n[t]&&(i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t])}else for(let t in r)i[t]=e?new e({type:`optional`,innerType:r[t]}):r[t];return ds(this,`shape`,i),i},checks:[]}))}function As(e,t,n){return xs(t,fs(t._zod.def,{get shape(){let r=t._zod.def.shape,i={...r};if(n)for(let t in n){if(!(t in i))throw Error(`Unrecognized key: "${t}"`);n[t]&&(i[t]=new e({type:`nonoptional`,innerType:r[t]}))}else for(let t in r)i[t]=new e({type:`nonoptional`,innerType:r[t]});return ds(this,`shape`,i),i}}))}function js(e,t=0){if(e.aborted===!0)return!0;for(let n=t;n{var n;return(n=t).path??(n.path=[]),t.path.unshift(e),t})}function Ns(e){return typeof e==`string`?e:e?.message}function Ps(e,t,n){let r={...e,path:e.path??[]};return e.message||(r.message=Ns(e.inst?._zod.def?.error?.(e))??Ns(t?.error?.(e))??Ns(n.customError?.(e))??Ns(n.localeError?.(e))??`Invalid input`),delete r.inst,delete r.continue,t?.reportInput||delete r.input,r}function Fs(e){return Array.isArray(e)?`array`:typeof e==`string`?`string`:`unknown`}function Is(...e){let[t,n,r]=e;return typeof t==`string`?{message:t,code:`custom`,input:n,inst:r}:{...t}}var Ls=(e,t)=>{e.name=`$ZodError`,Object.defineProperty(e,`_zod`,{value:e._zod,enumerable:!1}),Object.defineProperty(e,`issues`,{value:t,enumerable:!1}),e.message=JSON.stringify(t,as,2),Object.defineProperty(e,`toString`,{value:()=>e.message,enumerable:!1})},Rs=K(`$ZodError`,Ls),zs=K(`$ZodError`,Ls,{Parent:Error});function Bs(e,t=e=>e.message){let n={},r=[];for(let i of e.issues)i.path.length>0?(n[i.path[0]]=n[i.path[0]]||[],n[i.path[0]].push(t(i))):r.push(t(i));return{formErrors:r,fieldErrors:n}}function Vs(e,t=e=>e.message){let n={_errors:[]},r=e=>{for(let i of e.issues)if(i.code===`invalid_union`&&i.errors.length)i.errors.map(e=>r({issues:e}));else if(i.code===`invalid_key`)r({issues:i.issues});else if(i.code===`invalid_element`)r({issues:i.issues});else if(i.path.length===0)n._errors.push(t(i));else{let e=n,r=0;for(;r(t,n,r,i)=>{let a=r?Object.assign(r,{async:!1}):{async:!1},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise)throw new es;if(o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>Ps(e,a,rs())));throw ms(t,i?.callee),t}return o.value},Us=e=>async(t,n,r,i)=>{let a=r?Object.assign(r,{async:!0}):{async:!0},o=t._zod.run({value:n,issues:[]},a);if(o instanceof Promise&&(o=await o),o.issues.length){let t=new(i?.Err??e)(o.issues.map(e=>Ps(e,a,rs())));throw ms(t,i?.callee),t}return o.value},Ws=e=>(t,n,r)=>{let i=r?{...r,async:!1}:{async:!1},a=t._zod.run({value:n,issues:[]},i);if(a instanceof Promise)throw new es;return a.issues.length?{success:!1,error:new(e??Rs)(a.issues.map(e=>Ps(e,i,rs())))}:{success:!0,data:a.value}},Gs=Ws(zs),Ks=e=>async(t,n,r)=>{let i=r?Object.assign(r,{async:!0}):{async:!0},a=t._zod.run({value:n,issues:[]},i);return a instanceof Promise&&(a=await a),a.issues.length?{success:!1,error:new e(a.issues.map(e=>Ps(e,i,rs())))}:{success:!0,data:a.value}},qs=Ks(zs),Js=e=>(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return Hs(e)(t,n,i)},Ys=e=>(t,n,r)=>Hs(e)(t,n,r),Xs=e=>async(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return Us(e)(t,n,i)},Zs=e=>async(t,n,r)=>Us(e)(t,n,r),Qs=e=>(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return Ws(e)(t,n,i)},$s=e=>(t,n,r)=>Ws(e)(t,n,r),ec=e=>async(t,n,r)=>{let i=r?Object.assign(r,{direction:`backward`}):{direction:`backward`};return Ks(e)(t,n,i)},tc=e=>async(t,n,r)=>Ks(e)(t,n,r),nc=/^-?\d+$/,rc=/^-?\d+(?:\.\d+)?$/,ic=K(`$ZodCheck`,(e,t)=>{var n;e._zod??={},e._zod.def=t,(n=e._zod).onattach??(n.onattach=[])}),ac={number:`number`,bigint:`bigint`,object:`date`},oc=K(`$ZodCheckLessThan`,(e,t)=>{ic.init(e,t);let n=ac[typeof t.value];e._zod.onattach.push(e=>{let n=e._zod.bag,r=(t.inclusive?n.maximum:n.exclusiveMaximum)??1/0;t.value{(t.inclusive?r.value<=t.value:r.value{ic.init(e,t);let n=ac[typeof t.value];e._zod.onattach.push(e=>{let n=e._zod.bag,r=(t.inclusive?n.minimum:n.exclusiveMinimum)??-1/0;t.value>r&&(t.inclusive?n.minimum=t.value:n.exclusiveMinimum=t.value)}),e._zod.check=r=>{(t.inclusive?r.value>=t.value:r.value>t.value)||r.issues.push({origin:n,code:`too_small`,minimum:typeof t.value==`object`?t.value.getTime():t.value,input:r.value,inclusive:t.inclusive,inst:e,continue:!t.abort})}}),cc=K(`$ZodCheckMultipleOf`,(e,t)=>{ic.init(e,t),e._zod.onattach.push(e=>{var n;(n=e._zod.bag).multipleOf??(n.multipleOf=t.value)}),e._zod.check=n=>{if(typeof n.value!=typeof t.value)throw Error(`Cannot mix number and bigint in multiple_of check.`);(typeof n.value==`bigint`?n.value%t.value===BigInt(0):ls(n.value,t.value)===0)||n.issues.push({origin:typeof n.value,code:`not_multiple_of`,divisor:t.value,input:n.value,inst:e,continue:!t.abort})}}),lc=K(`$ZodCheckNumberFormat`,(e,t)=>{ic.init(e,t),t.format=t.format||`float64`;let n=t.format?.includes(`int`),r=n?`int`:`number`,[i,a]=Cs[t.format];e._zod.onattach.push(e=>{let r=e._zod.bag;r.format=t.format,r.minimum=i,r.maximum=a,n&&(r.pattern=nc)}),e._zod.check=o=>{let s=o.value;if(n){if(!Number.isInteger(s)){o.issues.push({expected:r,format:t.format,code:`invalid_type`,continue:!1,input:s,inst:e});return}if(!Number.isSafeInteger(s)){s>0?o.issues.push({input:s,code:`too_big`,maximum:2**53-1,note:`Integers must be within the safe integer range.`,inst:e,origin:r,inclusive:!0,continue:!t.abort}):o.issues.push({input:s,code:`too_small`,minimum:-(2**53-1),note:`Integers must be within the safe integer range.`,inst:e,origin:r,inclusive:!0,continue:!t.abort});return}}sa&&o.issues.push({origin:`number`,input:s,code:`too_big`,maximum:a,inclusive:!0,inst:e,continue:!t.abort})}}),uc=K(`$ZodCheckMaxLength`,(e,t)=>{var n;ic.init(e,t),(n=e._zod.def).when??(n.when=e=>{let t=e.value;return!ss(t)&&t.length!==void 0}),e._zod.onattach.push(e=>{let n=e._zod.bag.maximum??1/0;t.maximum{let r=n.value;if(r.length<=t.maximum)return;let i=Fs(r);n.issues.push({origin:i,code:`too_big`,maximum:t.maximum,inclusive:!0,input:r,inst:e,continue:!t.abort})}}),dc=K(`$ZodCheckMinLength`,(e,t)=>{var n;ic.init(e,t),(n=e._zod.def).when??(n.when=e=>{let t=e.value;return!ss(t)&&t.length!==void 0}),e._zod.onattach.push(e=>{let n=e._zod.bag.minimum??-1/0;t.minimum>n&&(e._zod.bag.minimum=t.minimum)}),e._zod.check=n=>{let r=n.value;if(r.length>=t.minimum)return;let i=Fs(r);n.issues.push({origin:i,code:`too_small`,minimum:t.minimum,inclusive:!0,input:r,inst:e,continue:!t.abort})}}),fc=K(`$ZodCheckLengthEquals`,(e,t)=>{var n;ic.init(e,t),(n=e._zod.def).when??(n.when=e=>{let t=e.value;return!ss(t)&&t.length!==void 0}),e._zod.onattach.push(e=>{let n=e._zod.bag;n.minimum=t.length,n.maximum=t.length,n.length=t.length}),e._zod.check=n=>{let r=n.value,i=r.length;if(i===t.length)return;let a=Fs(r),o=i>t.length;n.issues.push({origin:a,...o?{code:`too_big`,maximum:t.length}:{code:`too_small`,minimum:t.length},inclusive:!0,exact:!0,input:n.value,inst:e,continue:!t.abort})}}),pc=K(`$ZodCheckOverwrite`,(e,t)=>{ic.init(e,t),e._zod.check=e=>{e.value=t.tx(e.value)}}),mc=class{constructor(e=[]){this.content=[],this.indent=0,this&&(this.args=e)}indented(e){this.indent+=1,e(this),--this.indent}write(e){if(typeof e==`function`){e(this,{execution:`sync`}),e(this,{execution:`async`});return}let t=e.split(` +`).filter(e=>e),n=Math.min(...t.map(e=>e.length-e.trimStart().length)),r=t.map(e=>e.slice(n)).map(e=>` `.repeat(this.indent*2)+e);for(let e of r)this.content.push(e)}compile(){let e=Function,t=this?.args,n=[...(this?.content??[``]).map(e=>` ${e}`)];return new e(...t,n.join(` +`))}},hc={major:4,minor:3,patch:6},Y=K(`$ZodType`,(e,t)=>{var n;e??={},e._zod.def=t,e._zod.bag=e._zod.bag||{},e._zod.version=hc;let r=[...e._zod.def.checks??[]];e._zod.traits.has(`$ZodCheck`)&&r.unshift(e);for(let t of r)for(let n of t._zod.onattach)n(e);if(r.length===0)(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred?.push(()=>{e._zod.run=e._zod.parse});else{let t=(e,t,n)=>{let r=js(e),i;for(let a of t){if(a._zod.def.when){if(!a._zod.def.when(e))continue}else if(r)continue;let t=e.issues.length,o=a._zod.check(e);if(o instanceof Promise&&n?.async===!1)throw new es;if(i||o instanceof Promise)i=(i??Promise.resolve()).then(async()=>{await o,e.issues.length!==t&&(r||=js(e,t))});else{if(e.issues.length===t)continue;r||=js(e,t)}}return i?i.then(()=>e):e},n=(n,i,a)=>{if(js(n))return n.aborted=!0,n;let o=t(i,r,a);if(o instanceof Promise){if(a.async===!1)throw new es;return o.then(t=>e._zod.parse(t,a))}return e._zod.parse(o,a)};e._zod.run=(i,a)=>{if(a.skipChecks)return e._zod.parse(i,a);if(a.direction===`backward`){let t=e._zod.parse({value:i.value,issues:[]},{...a,skipChecks:!0});return t instanceof Promise?t.then(e=>n(e,i,a)):n(t,i,a)}let o=e._zod.parse(i,a);if(o instanceof Promise){if(a.async===!1)throw new es;return o.then(e=>t(e,r,a))}return t(o,r,a)}}q(e,`~standard`,()=>({validate:t=>{try{let n=Gs(e,t);return n.success?{value:n.data}:{issues:n.error?.issues}}catch{return qs(e,t).then(e=>e.success?{value:e.data}:{issues:e.error?.issues})}},vendor:`zod`,version:1}))}),gc=K(`$ZodNumber`,(e,t)=>{Y.init(e,t),e._zod.pattern=e._zod.bag.pattern??rc,e._zod.parse=(n,r)=>{if(t.coerce)try{n.value=Number(n.value)}catch{}let i=n.value;if(typeof i==`number`&&!Number.isNaN(i)&&Number.isFinite(i))return n;let a=typeof i==`number`?Number.isNaN(i)?`NaN`:Number.isFinite(i)?void 0:`Infinity`:void 0;return n.issues.push({expected:`number`,code:`invalid_type`,input:i,inst:e,...a?{received:a}:{}}),n}}),_c=K(`$ZodNumberFormat`,(e,t)=>{lc.init(e,t),gc.init(e,t)}),vc=K(`$ZodUnknown`,(e,t)=>{Y.init(e,t),e._zod.parse=e=>e}),yc=K(`$ZodNever`,(e,t)=>{Y.init(e,t),e._zod.parse=(t,n)=>(t.issues.push({expected:`never`,code:`invalid_type`,input:t.value,inst:e}),t)});function bc(e,t,n){e.issues.length&&t.issues.push(...Ms(n,e.issues)),t.value[n]=e.value}var xc=K(`$ZodArray`,(e,t)=>{Y.init(e,t),e._zod.parse=(n,r)=>{let i=n.value;if(!Array.isArray(i))return n.issues.push({expected:`array`,code:`invalid_type`,input:i,inst:e}),n;n.value=Array(i.length);let a=[];for(let e=0;ebc(t,n,e))):bc(s,n,e)}return a.length?Promise.all(a).then(()=>n):n}});function Sc(e,t,n,r,i){if(e.issues.length){if(i&&!(n in r))return;t.issues.push(...Ms(n,e.issues))}e.value===void 0?n in r&&(t.value[n]=void 0):t.value[n]=e.value}function Cc(e){let t=Object.keys(e.shape);for(let n of t)if(!e.shape?.[n]?._zod?.traits?.has(`$ZodType`))throw Error(`Invalid element at key "${n}": expected a Zod schema`);let n=Ss(e.shape);return{...e,keys:t,keySet:new Set(t),numKeys:t.length,optionalKeys:new Set(n)}}function wc(e,t,n,r,i,a){let o=[],s=i.keySet,c=i.catchall._zod,l=c.def.type,u=c.optout===`optional`;for(let i in t){if(s.has(i))continue;if(l===`never`){o.push(i);continue}let a=c.run({value:t[i],issues:[]},r);a instanceof Promise?e.push(a.then(e=>Sc(e,n,i,t,u))):Sc(a,n,i,t,u)}return o.length&&n.issues.push({code:`unrecognized_keys`,keys:o,input:t,inst:a}),e.length?Promise.all(e).then(()=>n):n}var Tc=K(`$ZodObject`,(e,t)=>{if(Y.init(e,t),!Object.getOwnPropertyDescriptor(t,`shape`)?.get){let e=t.shape;Object.defineProperty(t,`shape`,{get:()=>{let n={...e};return Object.defineProperty(t,`shape`,{value:n}),n}})}let n=os(()=>Cc(t));q(e._zod,`propValues`,()=>{let e=t.shape,n={};for(let t in e){let r=e[t]._zod;if(r.values){n[t]??(n[t]=new Set);for(let e of r.values)n[t].add(e)}}return n});let r=hs,i=t.catchall,a;e._zod.parse=(t,o)=>{a??=n.value;let s=t.value;if(!r(s))return t.issues.push({expected:`object`,code:`invalid_type`,input:s,inst:e}),t;t.value={};let c=[],l=a.shape;for(let e of a.keys){let n=l[e],r=n._zod.optout===`optional`,i=n._zod.run({value:s[e],issues:[]},o);i instanceof Promise?c.push(i.then(n=>Sc(n,t,e,s,r))):Sc(i,t,e,s,r)}return i?wc(c,s,t,o,n.value,e):c.length?Promise.all(c).then(()=>t):t}}),Ec=K(`$ZodObjectJIT`,(e,t)=>{Tc.init(e,t);let n=e._zod.parse,r=os(()=>Cc(t)),i=e=>{let t=new mc([`shape`,`payload`,`ctx`]),n=r.value,i=e=>{let t=ps(e);return`shape[${t}]._zod.run({ value: input[${t}], issues: [] }, ctx)`};t.write(`const input = payload.value;`);let a=Object.create(null),o=0;for(let e of n.keys)a[e]=`key_${o++}`;t.write(`const newResult = {};`);for(let r of n.keys){let n=a[r],o=ps(r),s=e[r]?._zod?.optout===`optional`;t.write(`const ${n} = ${i(r)};`),s?t.write(` + if (${n}.issues.length) { + if (${o} in input) { + payload.issues = payload.issues.concat(${n}.issues.map(iss => ({ + ...iss, + path: iss.path ? [${o}, ...iss.path] : [${o}] + }))); + } + } + + if (${n}.value === undefined) { + if (${o} in input) { + newResult[${o}] = undefined; + } + } else { + newResult[${o}] = ${n}.value; + } + + `):t.write(` + if (${n}.issues.length) { + payload.issues = payload.issues.concat(${n}.issues.map(iss => ({ + ...iss, + path: iss.path ? [${o}, ...iss.path] : [${o}] + }))); + } + + if (${n}.value === undefined) { + if (${o} in input) { + newResult[${o}] = undefined; + } + } else { + newResult[${o}] = ${n}.value; + } + + `)}t.write(`payload.value = newResult;`),t.write(`return payload;`);let s=t.compile();return(t,n)=>s(e,t,n)},a,o=hs,s=!ns.jitless,c=s&&gs.value,l=t.catchall,u;e._zod.parse=(d,f)=>{u??=r.value;let p=d.value;return o(p)?s&&c&&f?.async===!1&&f.jitless!==!0?(a||=i(t.shape),d=a(d,f),l?wc([],p,d,f,u,e):d):n(d,f):(d.issues.push({expected:`object`,code:`invalid_type`,input:p,inst:e}),d)}});function Dc(e,t,n,r){for(let n of e)if(n.issues.length===0)return t.value=n.value,t;let i=e.filter(e=>!js(e));return i.length===1?(t.value=i[0].value,i[0]):(t.issues.push({code:`invalid_union`,input:t.value,inst:n,errors:e.map(e=>e.issues.map(e=>Ps(e,r,rs())))}),t)}var Oc=K(`$ZodUnion`,(e,t)=>{Y.init(e,t),q(e._zod,`optin`,()=>t.options.some(e=>e._zod.optin===`optional`)?`optional`:void 0),q(e._zod,`optout`,()=>t.options.some(e=>e._zod.optout===`optional`)?`optional`:void 0),q(e._zod,`values`,()=>{if(t.options.every(e=>e._zod.values))return new Set(t.options.flatMap(e=>Array.from(e._zod.values)))}),q(e._zod,`pattern`,()=>{if(t.options.every(e=>e._zod.pattern)){let e=t.options.map(e=>e._zod.pattern);return RegExp(`^(${e.map(e=>cs(e.source)).join(`|`)})$`)}});let n=t.options.length===1,r=t.options[0]._zod.run;e._zod.parse=(i,a)=>{if(n)return r(i,a);let o=!1,s=[];for(let e of t.options){let t=e._zod.run({value:i.value,issues:[]},a);if(t instanceof Promise)s.push(t),o=!0;else{if(t.issues.length===0)return t;s.push(t)}}return o?Promise.all(s).then(t=>Dc(t,i,e,a)):Dc(s,i,e,a)}}),kc=K(`$ZodDiscriminatedUnion`,(e,t)=>{t.inclusive=!1,Oc.init(e,t);let n=e._zod.parse;q(e._zod,`propValues`,()=>{let e={};for(let n of t.options){let r=n._zod.propValues;if(!r||Object.keys(r).length===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(n)}"`);for(let[t,n]of Object.entries(r)){e[t]||(e[t]=new Set);for(let r of n)e[t].add(r)}}return e});let r=os(()=>{let e=t.options,n=new Map;for(let r of e){let e=r._zod.propValues?.[t.discriminator];if(!e||e.size===0)throw Error(`Invalid discriminated union option at index "${t.options.indexOf(r)}"`);for(let t of e){if(n.has(t))throw Error(`Duplicate discriminator value "${String(t)}"`);n.set(t,r)}}return n});e._zod.parse=(i,a)=>{let o=i.value;if(!hs(o))return i.issues.push({code:`invalid_type`,expected:`object`,input:o,inst:e}),i;let s=r.value.get(o?.[t.discriminator]);return s?s._zod.run(i,a):t.unionFallback?n(i,a):(i.issues.push({code:`invalid_union`,errors:[],note:`No matching discriminator`,discriminator:t.discriminator,input:o,path:[t.discriminator],inst:e}),i)}}),Ac=K(`$ZodIntersection`,(e,t)=>{Y.init(e,t),e._zod.parse=(e,n)=>{let r=e.value,i=t.left._zod.run({value:r,issues:[]},n),a=t.right._zod.run({value:r,issues:[]},n);return i instanceof Promise||a instanceof Promise?Promise.all([i,a]).then(([t,n])=>Mc(e,t,n)):Mc(e,i,a)}});function jc(e,t){if(e===t||e instanceof Date&&t instanceof Date&&+e==+t)return{valid:!0,data:e};if(_s(e)&&_s(t)){let n=Object.keys(t),r=Object.keys(e).filter(e=>n.indexOf(e)!==-1),i={...e,...t};for(let n of r){let r=jc(e[n],t[n]);if(!r.valid)return{valid:!1,mergeErrorPath:[n,...r.mergeErrorPath]};i[n]=r.data}return{valid:!0,data:i}}if(Array.isArray(e)&&Array.isArray(t)){if(e.length!==t.length)return{valid:!1,mergeErrorPath:[]};let n=[];for(let r=0;re.l&&e.r).map(([e])=>e);if(a.length&&i&&e.issues.push({...i,keys:a}),js(e))return e;let o=jc(t.value,n.value);if(!o.valid)throw Error(`Unmergable intersection. Error path: ${JSON.stringify(o.mergeErrorPath)}`);return e.value=o.data,e}var Nc=K(`$ZodEnum`,(e,t)=>{Y.init(e,t);let n=is(t.entries),r=new Set(n);e._zod.values=r,e._zod.pattern=RegExp(`^(${n.filter(e=>ys.has(typeof e)).map(e=>typeof e==`string`?bs(e):e.toString()).join(`|`)})$`),e._zod.parse=(t,i)=>{let a=t.value;return r.has(a)||t.issues.push({code:`invalid_value`,values:n,input:a,inst:e}),t}}),Pc=K(`$ZodLiteral`,(e,t)=>{if(Y.init(e,t),t.values.length===0)throw Error(`Cannot create literal schema with no valid values`);let n=new Set(t.values);e._zod.values=n,e._zod.pattern=RegExp(`^(${t.values.map(e=>typeof e==`string`?bs(e):e?bs(e.toString()):String(e)).join(`|`)})$`),e._zod.parse=(r,i)=>{let a=r.value;return n.has(a)||r.issues.push({code:`invalid_value`,values:t.values,input:a,inst:e}),r}}),Fc=K(`$ZodTransform`,(e,t)=>{Y.init(e,t),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new ts(e.constructor.name);let i=t.transform(n.value,n);if(r.async)return(i instanceof Promise?i:Promise.resolve(i)).then(e=>(n.value=e,n));if(i instanceof Promise)throw new es;return n.value=i,n}});function Ic(e,t){return e.issues.length&&t===void 0?{issues:[],value:void 0}:e}var Lc=K(`$ZodOptional`,(e,t)=>{Y.init(e,t),e._zod.optin=`optional`,e._zod.optout=`optional`,q(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,void 0]):void 0),q(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${cs(e.source)})?$`):void 0}),e._zod.parse=(e,n)=>{if(t.innerType._zod.optin===`optional`){let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(t=>Ic(t,e.value)):Ic(r,e.value)}return e.value===void 0?e:t.innerType._zod.run(e,n)}}),Rc=K(`$ZodExactOptional`,(e,t)=>{Lc.init(e,t),q(e._zod,`values`,()=>t.innerType._zod.values),q(e._zod,`pattern`,()=>t.innerType._zod.pattern),e._zod.parse=(e,n)=>t.innerType._zod.run(e,n)}),zc=K(`$ZodNullable`,(e,t)=>{Y.init(e,t),q(e._zod,`optin`,()=>t.innerType._zod.optin),q(e._zod,`optout`,()=>t.innerType._zod.optout),q(e._zod,`pattern`,()=>{let e=t.innerType._zod.pattern;return e?RegExp(`^(${cs(e.source)}|null)$`):void 0}),q(e._zod,`values`,()=>t.innerType._zod.values?new Set([...t.innerType._zod.values,null]):void 0),e._zod.parse=(e,n)=>e.value===null?e:t.innerType._zod.run(e,n)}),Bc=K(`$ZodDefault`,(e,t)=>{Y.init(e,t),e._zod.optin=`optional`,q(e._zod,`values`,()=>t.innerType._zod.values),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);if(e.value===void 0)return e.value=t.defaultValue,e;let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(e=>Vc(e,t)):Vc(r,t)}});function Vc(e,t){return e.value===void 0&&(e.value=t.defaultValue),e}var Hc=K(`$ZodPrefault`,(e,t)=>{Y.init(e,t),e._zod.optin=`optional`,q(e._zod,`values`,()=>t.innerType._zod.values),e._zod.parse=(e,n)=>(n.direction===`backward`||e.value===void 0&&(e.value=t.defaultValue),t.innerType._zod.run(e,n))}),Uc=K(`$ZodNonOptional`,(e,t)=>{Y.init(e,t),q(e._zod,`values`,()=>{let e=t.innerType._zod.values;return e?new Set([...e].filter(e=>e!==void 0)):void 0}),e._zod.parse=(n,r)=>{let i=t.innerType._zod.run(n,r);return i instanceof Promise?i.then(t=>Wc(t,e)):Wc(i,e)}});function Wc(e,t){return!e.issues.length&&e.value===void 0&&e.issues.push({code:`invalid_type`,expected:`nonoptional`,input:e.value,inst:t}),e}var Gc=K(`$ZodCatch`,(e,t)=>{Y.init(e,t),q(e._zod,`optin`,()=>t.innerType._zod.optin),q(e._zod,`optout`,()=>t.innerType._zod.optout),q(e._zod,`values`,()=>t.innerType._zod.values),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(r=>(e.value=r.value,r.issues.length&&(e.value=t.catchValue({...e,error:{issues:r.issues.map(e=>Ps(e,n,rs()))},input:e.value}),e.issues=[]),e)):(e.value=r.value,r.issues.length&&(e.value=t.catchValue({...e,error:{issues:r.issues.map(e=>Ps(e,n,rs()))},input:e.value}),e.issues=[]),e)}}),Kc=K(`$ZodPipe`,(e,t)=>{Y.init(e,t),q(e._zod,`values`,()=>t.in._zod.values),q(e._zod,`optin`,()=>t.in._zod.optin),q(e._zod,`optout`,()=>t.out._zod.optout),q(e._zod,`propValues`,()=>t.in._zod.propValues),e._zod.parse=(e,n)=>{if(n.direction===`backward`){let r=t.out._zod.run(e,n);return r instanceof Promise?r.then(e=>qc(e,t.in,n)):qc(r,t.in,n)}let r=t.in._zod.run(e,n);return r instanceof Promise?r.then(e=>qc(e,t.out,n)):qc(r,t.out,n)}});function qc(e,t,n){return e.issues.length?(e.aborted=!0,e):t._zod.run({value:e.value,issues:e.issues},n)}var Jc=K(`$ZodReadonly`,(e,t)=>{Y.init(e,t),q(e._zod,`propValues`,()=>t.innerType._zod.propValues),q(e._zod,`values`,()=>t.innerType._zod.values),q(e._zod,`optin`,()=>t.innerType?._zod?.optin),q(e._zod,`optout`,()=>t.innerType?._zod?.optout),e._zod.parse=(e,n)=>{if(n.direction===`backward`)return t.innerType._zod.run(e,n);let r=t.innerType._zod.run(e,n);return r instanceof Promise?r.then(Yc):Yc(r)}});function Yc(e){return e.value=Object.freeze(e.value),e}var Xc=K(`$ZodCustom`,(e,t)=>{ic.init(e,t),Y.init(e,t),e._zod.parse=(e,t)=>e,e._zod.check=n=>{let r=n.value,i=t.fn(r);if(i instanceof Promise)return i.then(t=>Zc(t,n,r,e));Zc(i,n,r,e)}});function Zc(e,t,n,r){if(!e){let e={code:`custom`,input:n,inst:r,path:[...r._zod.def.path??[]],continue:!r._zod.def.abort};r._zod.def.params&&(e.params=r._zod.def.params),t.issues.push(Is(e))}}var Qc,$c=class{constructor(){this._map=new WeakMap,this._idmap=new Map}add(e,...t){let n=t[0];return this._map.set(e,n),n&&typeof n==`object`&&`id`in n&&this._idmap.set(n.id,e),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(e){let t=this._map.get(e);return t&&typeof t==`object`&&`id`in t&&this._idmap.delete(t.id),this._map.delete(e),this}get(e){let t=e._zod.parent;if(t){let n={...this.get(t)??{}};delete n.id;let r={...n,...this._map.get(e)};return Object.keys(r).length?r:void 0}return this._map.get(e)}has(e){return this._map.has(e)}};function el(){return new $c}(Qc=globalThis).__zod_globalRegistry??(Qc.__zod_globalRegistry=el());var tl=globalThis.__zod_globalRegistry;function nl(e,t){return new e({type:`number`,checks:[],...J(t)})}function rl(e,t){return new e({type:`number`,check:`number_format`,abort:!1,format:`safeint`,...J(t)})}function il(e){return new e({type:`unknown`})}function al(e,t){return new e({type:`never`,...J(t)})}function ol(e,t){return new oc({check:`less_than`,...J(t),value:e,inclusive:!1})}function sl(e,t){return new oc({check:`less_than`,...J(t),value:e,inclusive:!0})}function cl(e,t){return new sc({check:`greater_than`,...J(t),value:e,inclusive:!1})}function ll(e,t){return new sc({check:`greater_than`,...J(t),value:e,inclusive:!0})}function ul(e,t){return new cc({check:`multiple_of`,...J(t),value:e})}function dl(e,t){return new uc({check:`max_length`,...J(t),maximum:e})}function fl(e,t){return new dc({check:`min_length`,...J(t),minimum:e})}function pl(e,t){return new fc({check:`length_equals`,...J(t),length:e})}function ml(e){return new pc({check:`overwrite`,tx:e})}function hl(e,t,n){return new e({type:`array`,element:t,...J(n)})}function gl(e,t,n){return new e({type:`custom`,check:`custom`,fn:t,...J(n)})}function _l(e){let t=vl(n=>(n.addIssue=e=>{if(typeof e==`string`)n.issues.push(Is(e,n.value,t._zod.def));else{let r=e;r.fatal&&(r.continue=!1),r.code??=`custom`,r.input??=n.value,r.inst??=t,r.continue??=!t._zod.def.abort,n.issues.push(Is(r))}},e(n.value,n)));return t}function vl(e,t){let n=new ic({check:`custom`,...J(t)});return n._zod.check=e,n}function yl(e){let t=e?.target??`draft-2020-12`;return t===`draft-4`&&(t=`draft-04`),t===`draft-7`&&(t=`draft-07`),{processors:e.processors??{},metadataRegistry:e?.metadata??tl,target:t,unrepresentable:e?.unrepresentable??`throw`,override:e?.override??(()=>{}),io:e?.io??`output`,counter:0,seen:new Map,cycles:e?.cycles??`ref`,reused:e?.reused??`inline`,external:e?.external??void 0}}function X(e,t,n={path:[],schemaPath:[]}){var r;let i=e._zod.def,a=t.seen.get(e);if(a)return a.count++,n.schemaPath.includes(e)&&(a.cycle=n.path),a.schema;let o={schema:{},count:1,cycle:void 0,path:n.path};t.seen.set(e,o);let s=e._zod.toJSONSchema?.();if(s)o.schema=s;else{let r={...n,schemaPath:[...n.schemaPath,e],path:n.path};if(e._zod.processJSONSchema)e._zod.processJSONSchema(t,o.schema,r);else{let n=o.schema,a=t.processors[i.type];if(!a)throw Error(`[toJSONSchema]: Non-representable type encountered: ${i.type}`);a(e,t,n,r)}let a=e._zod.parent;a&&(o.ref||=a,X(a,t,r),t.seen.get(a).isParent=!0)}let c=t.metadataRegistry.get(e);return c&&Object.assign(o.schema,c),t.io===`input`&&Z(e)&&(delete o.schema.examples,delete o.schema.default),t.io===`input`&&o.schema._prefault&&((r=o.schema).default??(r.default=o.schema._prefault)),delete o.schema._prefault,t.seen.get(e).schema}function bl(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=new Map;for(let t of e.seen.entries()){let n=e.metadataRegistry.get(t[0])?.id;if(n){let e=r.get(n);if(e&&e!==t[0])throw Error(`Duplicate schema id "${n}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);r.set(n,t[0])}}let i=t=>{let r=e.target===`draft-2020-12`?`$defs`:`definitions`;if(e.external){let n=e.external.registry.get(t[0])?.id,i=e.external.uri??(e=>e);if(n)return{ref:i(n)};let a=t[1].defId??t[1].schema.id??`schema${e.counter++}`;return t[1].defId=a,{defId:a,ref:`${i(`__shared`)}#/${r}/${a}`}}if(t[1]===n)return{ref:`#`};let i=`#/${r}/`,a=t[1].schema.id??`__schema${e.counter++}`;return{defId:a,ref:i+a}},a=e=>{if(e[1].schema.$ref)return;let t=e[1],{ref:n,defId:r}=i(e);t.def={...t.schema},r&&(t.defId=r);let a=t.schema;for(let e in a)delete a[e];a.$ref=n};if(e.cycles===`throw`)for(let t of e.seen.entries()){let e=t[1];if(e.cycle)throw Error(`Cycle detected: #/${e.cycle?.join(`/`)}/ + +Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(let n of e.seen.entries()){let r=n[1];if(t===n[0]){a(n);continue}if(e.external){let r=e.external.registry.get(n[0])?.id;if(t!==n[0]&&r){a(n);continue}}if(e.metadataRegistry.get(n[0])?.id){a(n);continue}if(r.cycle){a(n);continue}if(r.count>1&&e.reused===`ref`){a(n);continue}}}function xl(e,t){let n=e.seen.get(t);if(!n)throw Error(`Unprocessed schema. This is a bug in Zod.`);let r=t=>{let n=e.seen.get(t);if(n.ref===null)return;let i=n.def??n.schema,a={...i},o=n.ref;if(n.ref=null,o){r(o);let n=e.seen.get(o),s=n.schema;if(s.$ref&&(e.target===`draft-07`||e.target===`draft-04`||e.target===`openapi-3.0`)?(i.allOf=i.allOf??[],i.allOf.push(s)):Object.assign(i,s),Object.assign(i,a),t._zod.parent===o)for(let e in i)e===`$ref`||e===`allOf`||e in a||delete i[e];if(s.$ref&&n.def)for(let e in i)e===`$ref`||e===`allOf`||e in n.def&&JSON.stringify(i[e])===JSON.stringify(n.def[e])&&delete i[e]}let s=t._zod.parent;if(s&&s!==o){r(s);let t=e.seen.get(s);if(t?.schema.$ref&&(i.$ref=t.schema.$ref,t.def))for(let e in i)e===`$ref`||e===`allOf`||e in t.def&&JSON.stringify(i[e])===JSON.stringify(t.def[e])&&delete i[e]}e.override({zodSchema:t,jsonSchema:i,path:n.path??[]})};for(let t of[...e.seen.entries()].reverse())r(t[0]);let i={};if(e.target===`draft-2020-12`?i.$schema=`https://json-schema.org/draft/2020-12/schema`:e.target===`draft-07`?i.$schema=`http://json-schema.org/draft-07/schema#`:e.target===`draft-04`?i.$schema=`http://json-schema.org/draft-04/schema#`:e.target,e.external?.uri){let n=e.external.registry.get(t)?.id;if(!n)throw Error("Schema is missing an `id` property");i.$id=e.external.uri(n)}Object.assign(i,n.def??n.schema);let a=e.external?.defs??{};for(let t of e.seen.entries()){let e=t[1];e.def&&e.defId&&(a[e.defId]=e.def)}e.external||Object.keys(a).length>0&&(e.target===`draft-2020-12`?i.$defs=a:i.definitions=a);try{let n=JSON.parse(JSON.stringify(i));return Object.defineProperty(n,`~standard`,{value:{...t[`~standard`],jsonSchema:{input:Cl(t,`input`,e.processors),output:Cl(t,`output`,e.processors)}},enumerable:!1,writable:!1}),n}catch{throw Error(`Error converting schema to JSON.`)}}function Z(e,t){let n=t??{seen:new Set};if(n.seen.has(e))return!1;n.seen.add(e);let r=e._zod.def;if(r.type===`transform`)return!0;if(r.type===`array`)return Z(r.element,n);if(r.type===`set`)return Z(r.valueType,n);if(r.type===`lazy`)return Z(r.getter(),n);if(r.type===`promise`||r.type===`optional`||r.type===`nonoptional`||r.type===`nullable`||r.type===`readonly`||r.type===`default`||r.type===`prefault`)return Z(r.innerType,n);if(r.type===`intersection`)return Z(r.left,n)||Z(r.right,n);if(r.type===`record`||r.type===`map`)return Z(r.keyType,n)||Z(r.valueType,n);if(r.type===`pipe`)return Z(r.in,n)||Z(r.out,n);if(r.type===`object`){for(let e in r.shape)if(Z(r.shape[e],n))return!0;return!1}if(r.type===`union`){for(let e of r.options)if(Z(e,n))return!0;return!1}if(r.type===`tuple`){for(let e of r.items)if(Z(e,n))return!0;return!!(r.rest&&Z(r.rest,n))}return!1}var Sl=(e,t={})=>n=>{let r=yl({...n,processors:t});return X(e,r),bl(r,e),xl(r,e)},Cl=(e,t,n={})=>r=>{let{libraryOptions:i,target:a}=r??{},o=yl({...i??{},target:a,io:t,processors:n});return X(e,o),bl(o,e),xl(o,e)},wl=(e,t,n,r)=>{let i=n,{minimum:a,maximum:o,format:s,multipleOf:c,exclusiveMaximum:l,exclusiveMinimum:u}=e._zod.bag;typeof s==`string`&&s.includes(`int`)?i.type=`integer`:i.type=`number`,typeof u==`number`&&(t.target===`draft-04`||t.target===`openapi-3.0`?(i.minimum=u,i.exclusiveMinimum=!0):i.exclusiveMinimum=u),typeof a==`number`&&(i.minimum=a,typeof u==`number`&&t.target!==`draft-04`&&(u>=a?delete i.minimum:delete i.exclusiveMinimum)),typeof l==`number`&&(t.target===`draft-04`||t.target===`openapi-3.0`?(i.maximum=l,i.exclusiveMaximum=!0):i.exclusiveMaximum=l),typeof o==`number`&&(i.maximum=o,typeof l==`number`&&t.target!==`draft-04`&&(l<=o?delete i.maximum:delete i.exclusiveMaximum)),typeof c==`number`&&(i.multipleOf=c)},Tl=(e,t,n,r)=>{n.not={}},El=(e,t,n,r)=>{let i=e._zod.def,a=is(i.entries);a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),n.enum=a},Dl=(e,t,n,r)=>{let i=e._zod.def,a=[];for(let e of i.values)if(e===void 0){if(t.unrepresentable===`throw`)throw Error("Literal `undefined` cannot be represented in JSON Schema")}else if(typeof e==`bigint`){if(t.unrepresentable===`throw`)throw Error(`BigInt literals cannot be represented in JSON Schema`);a.push(Number(e))}else a.push(e);if(a.length!==0)if(a.length===1){let e=a[0];n.type=e===null?`null`:typeof e,t.target===`draft-04`||t.target===`openapi-3.0`?n.enum=[e]:n.const=e}else a.every(e=>typeof e==`number`)&&(n.type=`number`),a.every(e=>typeof e==`string`)&&(n.type=`string`),a.every(e=>typeof e==`boolean`)&&(n.type=`boolean`),a.every(e=>e===null)&&(n.type=`null`),n.enum=a},Ol=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Custom types cannot be represented in JSON Schema`)},kl=(e,t,n,r)=>{if(t.unrepresentable===`throw`)throw Error(`Transforms cannot be represented in JSON Schema`)},Al=(e,t,n,r)=>{let i=n,a=e._zod.def,{minimum:o,maximum:s}=e._zod.bag;typeof o==`number`&&(i.minItems=o),typeof s==`number`&&(i.maxItems=s),i.type=`array`,i.items=X(a.element,t,{...r,path:[...r.path,`items`]})},jl=(e,t,n,r)=>{let i=n,a=e._zod.def;i.type=`object`,i.properties={};let o=a.shape;for(let e in o)i.properties[e]=X(o[e],t,{...r,path:[...r.path,`properties`,e]});let s=new Set(Object.keys(o)),c=new Set([...s].filter(e=>{let n=a.shape[e]._zod;return t.io===`input`?n.optin===void 0:n.optout===void 0}));c.size>0&&(i.required=Array.from(c)),a.catchall?._zod.def.type===`never`?i.additionalProperties=!1:a.catchall?a.catchall&&(i.additionalProperties=X(a.catchall,t,{...r,path:[...r.path,`additionalProperties`]})):t.io===`output`&&(i.additionalProperties=!1)},Ml=(e,t,n,r)=>{let i=e._zod.def,a=i.inclusive===!1,o=i.options.map((e,n)=>X(e,t,{...r,path:[...r.path,a?`oneOf`:`anyOf`,n]}));a?n.oneOf=o:n.anyOf=o},Nl=(e,t,n,r)=>{let i=e._zod.def,a=X(i.left,t,{...r,path:[...r.path,`allOf`,0]}),o=X(i.right,t,{...r,path:[...r.path,`allOf`,1]}),s=e=>`allOf`in e&&Object.keys(e).length===1;n.allOf=[...s(a)?a.allOf:[a],...s(o)?o.allOf:[o]]},Pl=(e,t,n,r)=>{let i=e._zod.def,a=X(i.innerType,t,r),o=t.seen.get(e);t.target===`openapi-3.0`?(o.ref=i.innerType,n.nullable=!0):n.anyOf=[a,{type:`null`}]},Fl=(e,t,n,r)=>{let i=e._zod.def;X(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},Il=(e,t,n,r)=>{let i=e._zod.def;X(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.default=JSON.parse(JSON.stringify(i.defaultValue))},Ll=(e,t,n,r)=>{let i=e._zod.def;X(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,t.io===`input`&&(n._prefault=JSON.parse(JSON.stringify(i.defaultValue)))},Rl=(e,t,n,r)=>{let i=e._zod.def;X(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType;let o;try{o=i.catchValue(void 0)}catch{throw Error(`Dynamic catch values are not supported in JSON Schema`)}n.default=o},zl=(e,t,n,r)=>{let i=e._zod.def,a=t.io===`input`?i.in._zod.def.type===`transform`?i.out:i.in:i.out;X(a,t,r);let o=t.seen.get(e);o.ref=a},Bl=(e,t,n,r)=>{let i=e._zod.def;X(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType,n.readOnly=!0},Vl=(e,t,n,r)=>{let i=e._zod.def;X(i.innerType,t,r);let a=t.seen.get(e);a.ref=i.innerType},Hl=(e,t)=>{Rs.init(e,t),e.name=`ZodError`,Object.defineProperties(e,{format:{value:t=>Vs(e,t)},flatten:{value:t=>Bs(e,t)},addIssue:{value:t=>{e.issues.push(t),e.message=JSON.stringify(e.issues,as,2)}},addIssues:{value:t=>{e.issues.push(...t),e.message=JSON.stringify(e.issues,as,2)}},isEmpty:{get(){return e.issues.length===0}}})};K(`ZodError`,Hl);var Q=K(`ZodError`,Hl,{Parent:Error}),Ul=Hs(Q),Wl=Us(Q),Gl=Ws(Q),Kl=Ks(Q),ql=Js(Q),Jl=Ys(Q),Yl=Xs(Q),Xl=Zs(Q),Zl=Qs(Q),Ql=$s(Q),$l=ec(Q),eu=tc(Q),$=K(`ZodType`,(e,t)=>(Y.init(e,t),Object.assign(e[`~standard`],{jsonSchema:{input:Cl(e,`input`),output:Cl(e,`output`)}}),e.toJSONSchema=Sl(e,{}),e.def=t,e.type=t.type,Object.defineProperty(e,`_def`,{value:t}),e.check=(...n)=>e.clone(fs(t,{checks:[...t.checks??[],...n.map(e=>typeof e==`function`?{_zod:{check:e,def:{check:`custom`},onattach:[]}}:e)]}),{parent:!0}),e.with=e.check,e.clone=(t,n)=>xs(e,t,n),e.brand=()=>e,e.register=((t,n)=>(t.add(e,n),e)),e.parse=(t,n)=>Ul(e,t,n,{callee:e.parse}),e.safeParse=(t,n)=>Gl(e,t,n),e.parseAsync=async(t,n)=>Wl(e,t,n,{callee:e.parseAsync}),e.safeParseAsync=async(t,n)=>Kl(e,t,n),e.spa=e.safeParseAsync,e.encode=(t,n)=>ql(e,t,n),e.decode=(t,n)=>Jl(e,t,n),e.encodeAsync=async(t,n)=>Yl(e,t,n),e.decodeAsync=async(t,n)=>Xl(e,t,n),e.safeEncode=(t,n)=>Zl(e,t,n),e.safeDecode=(t,n)=>Ql(e,t,n),e.safeEncodeAsync=async(t,n)=>$l(e,t,n),e.safeDecodeAsync=async(t,n)=>eu(e,t,n),e.refine=(t,n)=>e.check(Wu(t,n)),e.superRefine=t=>e.check(Gu(t)),e.overwrite=t=>e.check(ml(t)),e.optional=()=>Eu(e),e.exactOptional=()=>Ou(e),e.nullable=()=>Au(e),e.nullish=()=>Eu(Au(e)),e.nonoptional=t=>Iu(e,t),e.array=()=>uu(e),e.or=t=>mu([e,t]),e.and=t=>vu(e,t),e.transform=t=>Bu(e,wu(t)),e.default=t=>Mu(e,t),e.prefault=t=>Pu(e,t),e.catch=t=>Ru(e,t),e.pipe=t=>Bu(e,t),e.readonly=()=>Hu(e),e.describe=t=>{let n=e.clone();return tl.add(n,{description:t}),n},Object.defineProperty(e,`description`,{get(){return tl.get(e)?.description},configurable:!0}),e.meta=(...t)=>{if(t.length===0)return tl.get(e);let n=e.clone();return tl.add(n,t[0]),n},e.isOptional=()=>e.safeParse(void 0).success,e.isNullable=()=>e.safeParse(null).success,e.apply=t=>t(e),e)),tu=K(`ZodNumber`,(e,t)=>{gc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>wl(e,t,n,r),e.gt=(t,n)=>e.check(cl(t,n)),e.gte=(t,n)=>e.check(ll(t,n)),e.min=(t,n)=>e.check(ll(t,n)),e.lt=(t,n)=>e.check(ol(t,n)),e.lte=(t,n)=>e.check(sl(t,n)),e.max=(t,n)=>e.check(sl(t,n)),e.int=t=>e.check(iu(t)),e.safe=t=>e.check(iu(t)),e.positive=t=>e.check(cl(0,t)),e.nonnegative=t=>e.check(ll(0,t)),e.negative=t=>e.check(ol(0,t)),e.nonpositive=t=>e.check(sl(0,t)),e.multipleOf=(t,n)=>e.check(ul(t,n)),e.step=(t,n)=>e.check(ul(t,n)),e.finite=()=>e;let n=e._zod.bag;e.minValue=Math.max(n.minimum??-1/0,n.exclusiveMinimum??-1/0)??null,e.maxValue=Math.min(n.maximum??1/0,n.exclusiveMaximum??1/0)??null,e.isInt=(n.format??``).includes(`int`)||Number.isSafeInteger(n.multipleOf??.5),e.isFinite=!0,e.format=n.format??null});function nu(e){return nl(tu,e)}var ru=K(`ZodNumberFormat`,(e,t)=>{_c.init(e,t),tu.init(e,t)});function iu(e){return rl(ru,e)}var au=K(`ZodUnknown`,(e,t)=>{vc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(e,t,n)=>void 0});function ou(){return il(au)}var su=K(`ZodNever`,(e,t)=>{yc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Tl(e,t,n,r)});function cu(e){return al(su,e)}var lu=K(`ZodArray`,(e,t)=>{xc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Al(e,t,n,r),e.element=t.element,e.min=(t,n)=>e.check(fl(t,n)),e.nonempty=t=>e.check(fl(1,t)),e.max=(t,n)=>e.check(dl(t,n)),e.length=(t,n)=>e.check(pl(t,n)),e.unwrap=()=>e.element});function uu(e,t){return hl(lu,e,t)}var du=K(`ZodObject`,(e,t)=>{Ec.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>jl(e,t,n,r),q(e,`shape`,()=>t.shape),e.keyof=()=>bu(Object.keys(e._zod.def.shape)),e.catchall=t=>e.clone({...e._zod.def,catchall:t}),e.passthrough=()=>e.clone({...e._zod.def,catchall:ou()}),e.loose=()=>e.clone({...e._zod.def,catchall:ou()}),e.strict=()=>e.clone({...e._zod.def,catchall:cu()}),e.strip=()=>e.clone({...e._zod.def,catchall:void 0}),e.extend=t=>Es(e,t),e.safeExtend=t=>Ds(e,t),e.merge=t=>Os(e,t),e.pick=t=>ws(e,t),e.omit=t=>Ts(e,t),e.partial=(...t)=>ks(Tu,e,t[0]),e.required=(...t)=>As(Fu,e,t[0])});function fu(e,t){return new du({type:`object`,shape:e??{},...J(t)})}var pu=K(`ZodUnion`,(e,t)=>{Oc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ml(e,t,n,r),e.options=t.options});function mu(e,t){return new pu({type:`union`,options:e,...J(t)})}var hu=K(`ZodDiscriminatedUnion`,(e,t)=>{pu.init(e,t),kc.init(e,t)});function gu(e,t,n){return new hu({type:`union`,options:t,discriminator:e,...J(n)})}var _u=K(`ZodIntersection`,(e,t)=>{Ac.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Nl(e,t,n,r)});function vu(e,t){return new _u({type:`intersection`,left:e,right:t})}var yu=K(`ZodEnum`,(e,t)=>{Nc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>El(e,t,n,r),e.enum=t.entries,e.options=Object.values(t.entries);let n=new Set(Object.keys(t.entries));e.extract=(e,r)=>{let i={};for(let r of e)if(n.has(r))i[r]=t.entries[r];else throw Error(`Key ${r} not found in enum`);return new yu({...t,checks:[],...J(r),entries:i})},e.exclude=(e,r)=>{let i={...t.entries};for(let t of e)if(n.has(t))delete i[t];else throw Error(`Key ${t} not found in enum`);return new yu({...t,checks:[],...J(r),entries:i})}});function bu(e,t){return new yu({type:`enum`,entries:Array.isArray(e)?Object.fromEntries(e.map(e=>[e,e])):e,...J(t)})}var xu=K(`ZodLiteral`,(e,t)=>{Pc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Dl(e,t,n,r),e.values=new Set(t.values),Object.defineProperty(e,`value`,{get(){if(t.values.length>1)throw Error("This schema contains multiple valid literal values. Use `.values` instead.");return t.values[0]}})});function Su(e,t){return new xu({type:`literal`,values:Array.isArray(e)?e:[e],...J(t)})}var Cu=K(`ZodTransform`,(e,t)=>{Fc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>kl(e,t,n,r),e._zod.parse=(n,r)=>{if(r.direction===`backward`)throw new ts(e.constructor.name);n.addIssue=r=>{if(typeof r==`string`)n.issues.push(Is(r,n.value,t));else{let t=r;t.fatal&&(t.continue=!1),t.code??=`custom`,t.input??=n.value,t.inst??=e,n.issues.push(Is(t))}};let i=t.transform(n.value,n);return i instanceof Promise?i.then(e=>(n.value=e,n)):(n.value=i,n)}});function wu(e){return new Cu({type:`transform`,transform:e})}var Tu=K(`ZodOptional`,(e,t)=>{Lc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Vl(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Eu(e){return new Tu({type:`optional`,innerType:e})}var Du=K(`ZodExactOptional`,(e,t)=>{Rc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Vl(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Ou(e){return new Du({type:`optional`,innerType:e})}var ku=K(`ZodNullable`,(e,t)=>{zc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Pl(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Au(e){return new ku({type:`nullable`,innerType:e})}var ju=K(`ZodDefault`,(e,t)=>{Bc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Il(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function Mu(e,t){return new ju({type:`default`,innerType:e,get defaultValue(){return typeof t==`function`?t():vs(t)}})}var Nu=K(`ZodPrefault`,(e,t)=>{Hc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ll(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Pu(e,t){return new Nu({type:`prefault`,innerType:e,get defaultValue(){return typeof t==`function`?t():vs(t)}})}var Fu=K(`ZodNonOptional`,(e,t)=>{Uc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Fl(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Iu(e,t){return new Fu({type:`nonoptional`,innerType:e,...J(t)})}var Lu=K(`ZodCatch`,(e,t)=>{Gc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Rl(e,t,n,r),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function Ru(e,t){return new Lu({type:`catch`,innerType:e,catchValue:typeof t==`function`?t:()=>t})}var zu=K(`ZodPipe`,(e,t)=>{Kc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>zl(e,t,n,r),e.in=t.in,e.out=t.out});function Bu(e,t){return new zu({type:`pipe`,in:e,out:t})}var Vu=K(`ZodReadonly`,(e,t)=>{Jc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Bl(e,t,n,r),e.unwrap=()=>e._zod.def.innerType});function Hu(e){return new Vu({type:`readonly`,innerType:e})}var Uu=K(`ZodCustom`,(e,t)=>{Xc.init(e,t),$.init(e,t),e._zod.processJSONSchema=(t,n,r)=>Ol(e,t,n,r)});function Wu(e,t={}){return gl(Uu,e,t)}function Gu(e){return _l(e)}gu(`type`,[fu({type:Su(`grid-inspect:activate`)}),fu({type:Su(`grid-inspect:deactivate`)}),fu({type:Su(`grid-inspect:highlight`),id:nu().finite()}),fu({type:Su(`grid-inspect:clear`)})]);var Ku=gu(`type`,[fu({type:Su(`grid-inspect:ready`)}),fu({type:Su(`grid-inspect:hover`),id:nu().finite()}),fu({type:Su(`grid-inspect:unhover`)}),fu({type:Su(`grid-inspect:missing`),id:nu().finite()})]);function qu(e){return{send(t){e()?.contentWindow?.postMessage(t,window.location.origin)},subscribe(e){let t=t=>{if(t.origin!==window.location.origin)return;let n=Ku.safeParse(t.data);n.success&&e(n.data)};return window.addEventListener(`message`,t),()=>window.removeEventListener(`message`,t)}}}function Ju(){return document.querySelector(`iframe[name="cms-preview-iframe"]`)}var Yu=200;function Xu({tree:t}){let{enabled:n,hover:r,setPreviewHover:i,clearHover:a,setMissing:o}=$o(),s=(0,e.useRef)(qu(Ju)),c=(0,e.useRef)(!1),l=(0,e.useRef)(!1);(0,e.useEffect)(()=>{let e=s.current;return e.subscribe(n=>{switch(n.type){case`grid-inspect:ready`:c.current=!0,l.current&&=(e.send({type:`grid-inspect:activate`}),!1);return;case`grid-inspect:hover`:{let e=Wo(t,n.id),r=Go(t,n.id);i(n.id,e,r);return}case`grid-inspect:unhover`:a();return;case`grid-inspect:missing`:o(!0);return}})},[t,i,a,o]),(0,e.useEffect)(()=>{let e=null,t=()=>{c.current=!1},n=n=>{n!==e&&(e?.removeEventListener(`load`,t),e=n,e?.addEventListener(`load`,t))};if(n(Ju()),typeof MutationObserver>`u`||typeof document>`u`)return()=>{e?.removeEventListener(`load`,t)};let r=new MutationObserver(()=>{n(Ju())});return r.observe(document.body,{childList:!0,subtree:!0}),()=>{r.disconnect(),e?.removeEventListener(`load`,t)}},[]),(0,e.useEffect)(()=>{let e=s.current;if(n){if(c.current){e.send({type:`grid-inspect:activate`});return}l.current=!0,e.send({type:`grid-inspect:deactivate`});return}e.send({type:`grid-inspect:deactivate`}),l.current=!1},[n]);let u=(0,e.useRef)(null);return(0,e.useEffect)(()=>{let e=s.current;if(!n){u.current=null;return}if(r===null||r.source!==`editor`){u.current!==null&&(e.send({type:`grid-inspect:clear`}),u.current=null);return}let t=r.id,i=window.setTimeout(()=>{e.send({type:`grid-inspect:highlight`,id:t}),u.current=t},Yu);return()=>window.clearTimeout(i)},[r,n]),null}function Zu(e){let t=new Map,n=e=>{t.set(e.id,e);let r=`children`in e?e.children:null;if(r!=null)for(let e of r)n(e)};for(let t of e.nodes)n(t);return t}function Qu({tree:t}){let{enabled:n,setEditorHover:r}=$o(),{activeType:i}=no(),a=(0,e.useMemo)(()=>Zu(t),[t]),o=(0,e.useRef)(null),s=(0,e.useRef)(i);return(0,e.useEffect)(()=>{s.current=i},[i]),(0,e.useEffect)(()=>{if(!n){o.current!==null&&(o.current=null,r(null));return}let e=e=>{if(s.current!==null)return;let t=e.target,n=(t instanceof Element?t:null)?.closest(`[data-node-id]`)??null;if(n===null){o.current!==null&&(o.current=null,r(null));return}let i=Number.parseInt(n.dataset.nodeId??``,10);if(!Number.isFinite(i)||i===o.current)return;let c=a.get(i);c!==void 0&&(o.current=i,r(c))},t=()=>{o.current!==null&&(o.current=null,r(null))};return document.addEventListener(`mousemove`,e,{passive:!0}),document.documentElement.addEventListener(`mouseleave`,t),()=>{document.removeEventListener(`mousemove`,e),document.documentElement.removeEventListener(`mouseleave`,t),o.current!==null&&(o.current=null,r(null))}},[n,a,r]),null}var $u=`inspect-halo`,ed=`inspect-breadcrumb`;function td(e){let t=document.querySelector(`[data-node-id="${e}"]`);if(t===null)return null;let n=t.getBoundingClientRect();return n.width===0&&n.height===0?null:t}function nd(e,t){let n=td(e);if(n!==null)return{target:n,isDirect:!0};for(let e=t.length-1;e>=0;e--){let n=td(t[e]);if(n!==null)return{target:n,isDirect:!1}}return null}function rd(e){return e.map(e=>({...e,isVisible:td(e.id)!==null}))}function id(e,t,n){let r=e.top-n-8<0?e.bottom+8:e.top-n-8,i=e.left,a=window.innerWidth-t-12;return{position:`fixed`,top:r,left:Math.max(12,Math.min(i,a)),zIndex:1e4}}function ad(){let{hover:t,missing:n}=$o(),[r,i]=(0,e.useState)(null);if((0,e.useEffect)(()=>{if(t===null||t.source!==`preview`){i(null);return}let e=nd(t.id,t.ancestorIds);i(e),e!==null&&e.target.scrollIntoView({behavior:`smooth`,block:`nearest`})},[t]),r===null||t===null||t.source!==`preview`)return null;let a=r.target.getBoundingClientRect(),o={position:`fixed`,top:a.top,left:a.left,width:a.width,height:a.height,pointerEvents:`none`,zIndex:9999},s=n?`${$u}--missing`:r.isDirect?``:`${$u}--indirect`,c=s===``?$u:`${$u} ${s}`,l=rd(t.path),u=l.length-1;return(0,v.jsxs)(e.Fragment,{children:[(0,v.jsx)(`div`,{className:c,"data-testid":`inspect-halo`,style:o}),l.length>0&&(0,v.jsx)(od,{segments:l,lastIndex:u,targetRect:a,missing:n})]})}function od({segments:t,lastIndex:n,targetRect:r,missing:i}){let[a,o]=(0,e.useState)(null),[s,c]=(0,e.useState)(null);(0,e.useEffect)(()=>{if(a===null)return;let{width:e,height:t}=a.getBoundingClientRect();c({w:e,h:t})},[a]);let l=s===null?{position:`fixed`,top:r.top-100,left:r.left,visibility:`hidden`,zIndex:1e4}:id(r,s.w,s.h);return(0,v.jsx)(`div`,{ref:o,className:i?`${ed} ${ed}--missing`:ed,"data-testid":`inspect-breadcrumb`,style:l,role:`status`,"aria-live":`polite`,children:t.map((t,r)=>{let i=r===n,a=[`${ed}__segment`,t.isVisible?``:`${ed}__segment--hidden`,i?`${ed}__segment--target`:``].filter(e=>e!==``).join(` `);return(0,v.jsxs)(e.Fragment,{children:[r>0&&(0,v.jsx)(`span`,{className:`${ed}__separator`,"aria-hidden":`true`,children:`›`}),(0,v.jsx)(`span`,{className:a,title:t.isVisible?t.title:`${t.title} (collapsed)`,"data-segment-type":t.type,"data-segment-hidden":t.isVisible?void 0:`true`,"data-segment-target":i?`true`:void 0,children:t.title||t.type})]},`${t.type}-${t.id}`)})})}var sd=1e3;function cd(){let{enabled:t,setEnabled:n}=$o(),[r,i]=(0,e.useState)(()=>Ju()!==null);(0,e.useEffect)(()=>{let e=window.setInterval(()=>{i(Ju()!==null)},sd);return()=>window.clearInterval(e)},[]);let a=h(`WeDevelopGrid.InspectToggle.LABEL`,`Inspect`),o=h(`WeDevelopGrid.InspectToggle.DISABLED_TITLE`,`Open split-mode preview to use inspect`);return(0,v.jsx)(`button`,{type:`button`,className:`inspect-toggle${t?` inspect-toggle--active`:``}`,"data-testid":`inspect-toggle`,"aria-pressed":t,disabled:!r,title:r?a:o,onClick:()=>n(!t),children:a})}function ld(){let{activeViewport:t}=Fo(),{pageId:n,zone:r}=Ro(),i=Na(n,r),{mutate:a}=ho(n,r),[o,s]=(0,e.useState)(!1),c=t===Do(),l=c?i._total??0:i[t]??0,u=Eo().find(e=>e.key===t)?.label??t,d=c?h(`WeDevelopGrid.useResetOverridesAction.RESET_ALL_LABEL`,`Reset all`):h(`WeDevelopGrid.useResetOverridesAction.RESET_VIEWPORT_LABEL`,`Reset viewport`),f=c?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_ALL`,`Reset all overrides`):h(`WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_VIEWPORT`,`Reset {viewport} overrides`,{viewport:u}),p=c&&l===1?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_ONE`,`Reset all viewport overrides across {count} column?`,{count:l}):c?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_MANY`,`Reset all viewport overrides across {count} columns?`,{count:l}):l===1?h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_ONE`,`Reset overrides for {count} column on {viewport}?`,{count:l,viewport:u}):h(`WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_MANY`,`Reset overrides for {count} columns on {viewport}?`,{count:l,viewport:u}),m=(0,e.useCallback)(()=>{s(!0)},[]),g=(0,e.useCallback)(()=>{s(!1)},[]),_=(0,e.useCallback)(()=>{s(!1),a(c?{pageId:n,zone:r}:{pageId:n,zone:r,viewport:t})},[c,n,r,t,a]);return{showReset:l>0,label:d,affectedCount:l,isDialogOpen:o,dialogTitle:f,dialogMessage:p,onResetClick:m,onConfirm:_,onCancel:g}}function ud({isOpen:t,title:n,message:r,confirmLabel:i,onConfirm:a,onCancel:o,destructive:s=!1}){let c=(0,e.useRef)(null);(0,e.useEffect)(()=>{let e=c.current;e!==null&&(t&&!e.open?e.showModal():!t&&e.open&&e.close())},[t]);let l=(0,e.useCallback)(()=>{o()},[o]);return(0,v.jsxs)(`dialog`,{ref:c,className:`confirm-dialog`,"data-testid":`confirm-dialog`,onClose:l,onClick:e=>{e.preventDefault(),e.stopPropagation()},children:[(0,v.jsx)(`div`,{className:`confirm-dialog__header`,children:(0,v.jsx)(`h3`,{className:`confirm-dialog__title`,children:n})}),(0,v.jsx)(`div`,{className:`confirm-dialog__body`,children:(0,v.jsx)(`p`,{className:`confirm-dialog__message`,children:r})}),(0,v.jsxs)(`div`,{className:`confirm-dialog__footer`,children:[(0,v.jsx)(`button`,{type:`button`,className:`confirm-dialog__button confirm-dialog__button--cancel`,onClick:l,children:h(`WeDevelopGrid.ConfirmDialog.CANCEL_BUTTON`,`Cancel`)}),(0,v.jsx)(`button`,{type:`button`,className:`confirm-dialog__button confirm-dialog__button--confirm${s?` confirm-dialog__button--destructive`:``}`,onClick:a,children:i})]})]})}function dd(){let e=Vo(),t=Eo(),{activeViewport:n,setActiveViewport:r}=Fo(),i=ld();return(0,v.jsxs)(`div`,{className:`viewport-switcher`,"data-testid":`viewport-switcher`,children:[(0,v.jsx)(`div`,{role:`group`,"aria-label":h(`WeDevelopGrid.ViewportSwitcher.GROUP_LABEL`,`Viewport size`),children:t.map(e=>{let t=e.key===n;return(0,v.jsx)(`button`,{type:`button`,className:`viewport-switcher__button${t?` viewport-switcher__button--active`:``}`,"data-testid":`viewport-button`,"aria-pressed":t,"aria-disabled":t||void 0,onClick:()=>{t||r(e.key)},children:e.label},e.key)})}),!e&&i.showReset&&(0,v.jsx)(`button`,{type:`button`,className:`viewport-switcher__reset`,"data-testid":`reset-overrides-button`,onClick:i.onResetClick,children:i.label}),!e&&(0,v.jsx)(ud,{isOpen:i.isDialogOpen,title:i.dialogTitle,message:i.dialogMessage,confirmLabel:h(`WeDevelopGrid.ViewportSwitcher.RESET_CONFIRM_LABEL`,`Reset`),onConfirm:i.onConfirm,onCancel:i.onCancel,destructive:!0})]})}function fd(e){return e.removedfromdraft===void 0?e.addedtodraft===void 0?e.modified===void 0?`published`:`modified`:`draft`:`removed`}function pd(e){return{"data-node-id":String(e.id)}}var md=.3;function hd(e,t,n){return{transform:Ut.Transform.toString(e),transition:t??void 0,opacity:n?md:void 0}}function gd(e,t,n){let r=[e,`${e}--${t}`];for(let[t,i]of Object.entries(n))i&&r.push(`${e}--${t}`);return r.join(` `)}function _d({listeners:e,attributes:t,label:n=h(`WeDevelopGrid.DragHandle.DEFAULT_LABEL`,`Drag to reorder`)}){return(0,v.jsx)(`button`,{type:`button`,className:`drag-handle`,"data-testid":`drag-handle`,"aria-label":n,...e,...t,children:(0,v.jsx)(`span`,{className:`drag-handle__icon`,"data-testid":`drag-handle-icon`,"aria-hidden":`true`})})}function vd({isCollapsed:e,onToggle:t,label:n}){return(0,v.jsx)(`button`,{type:`button`,className:`collapse-toggle${e?` collapse-toggle--collapsed`:``}`,"aria-expanded":!e,"aria-label":e?h(`WeDevelopGrid.CollapseToggle.EXPAND_LABEL`,`Expand {label}`,{label:n}):h(`WeDevelopGrid.CollapseToggle.COLLAPSE_LABEL`,`Collapse {label}`,{label:n}),"data-testid":`collapse-toggle`,onClick:e=>{e.stopPropagation(),t()},children:(0,v.jsx)(`span`,{className:`collapse-toggle__chevron`,"aria-hidden":`true`})})}function yd(e){if(!Ki(e)||e.children===null)return 0;let t=0;for(let n of e.children)t+=1+yd(n);return t}function bd(e,t){return t===0?h(`WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_SIMPLE`,`Archive "{title}"?`,{title:e}):t===1?h(`WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_ONE_CHILD`,`Archive "{title}" and all {count} child element?`,{title:e,count:t}):h(`WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_MANY_CHILDREN`,`Archive "{title}" and all {count} child elements?`,{title:e,count:t})}function xd(t){let{pageId:n,zone:r}=Ro(),i=uo(n,r),[a,o]=(0,e.useState)(!1),s=yd(t),c=(0,e.useCallback)(()=>{o(!0)},[]),l=(0,e.useCallback)(()=>{o(!1)},[]),u=(0,e.useCallback)(()=>{o(!1),i.mutate(t.id,{onError:e=>{p(e.message)}})},[i,t.id]);return t.canDelete?{action:{key:`archive`,label:h(`WeDevelopGrid.useArchiveAction.ACTION_LABEL`,`Archive`),destructive:!0,onAction:c},dialog:{isOpen:a,title:h(`WeDevelopGrid.useArchiveAction.DIALOG_TITLE`,`Confirm archive`),message:bd(t.title,s),onConfirm:u,onCancel:l}}:{action:null,dialog:null}}function Sd(t){let{pageId:n,zone:r}=Ro(),i=fo(n,r),a=(0,e.useCallback)(()=>{i.mutate(t.id,{onError:e=>{p(e.message)}})},[i,t.id]);return t.canCreate?{action:{key:`duplicate`,label:h(`WeDevelopGrid.useDuplicateAction.ACTION_LABEL`,`Duplicate`),onAction:a}}:{action:null}}function Cd(e){return Ki(e)?e.containerType:`element`}function wd(t){let{pageId:n,zone:r}=Ro(),i=po(n,r),[a,o]=(0,e.useState)(!1),[s,c]=(0,e.useState)(null),l=(0,e.useCallback)(()=>{c(null),o(!0)},[]),u=(0,e.useCallback)(()=>{o(!1),c(null)},[]),d=(0,e.useCallback)((e,n,r)=>{i.mutate({id:t.id,targetPageId:e,targetZone:n,targetParent:r},{onSuccess:()=>{o(!1),c(null)},onError:e=>{c(e.message)}})},[i,t.id]);return t.canCreate?{action:{key:`duplicate-to`,label:h(`WeDevelopGrid.useDuplicateToAction.ACTION_LABEL`,`Duplicate to…`),onAction:l},dialog:{isOpen:a,elementType:Cd(t),onConfirm:d,onCancel:u,error:s}}:{action:null,dialog:null}}function Td({actions:t,testId:n=`actions-menu`}){let[r,i]=(0,e.useState)(!1),[a,o]=(0,e.useState)(0),s=(0,e.useRef)(null),c=(0,e.useRef)(null),l=(0,e.useRef)(null),u=(0,e.useId)(),d=(0,e.useCallback)(e=>`${u}item-${e}`,[u]),f=(0,e.useCallback)(()=>i(!1),[]);(0,e.useEffect)(()=>{r&&(o(0),l.current?.focus())},[r]),(0,e.useEffect)(()=>{if(!r)return;function e(e){s.current&&!s.current.contains(e.target)&&f()}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[r,f]),(0,e.useEffect)(()=>{if(!r)return;function e(e){e.key===`Escape`&&(e.stopPropagation(),f(),c.current?.focus())}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[r,f]);function p(e){e.preventDefault(),e.stopPropagation(),i(e=>!e)}function m(e,t){e.preventDefault(),e.stopPropagation(),t(),f()}function g(e){let n=t.length-1;switch(e.key){case`ArrowDown`:e.preventDefault(),o(e=>e>=n?n:e+1);return;case`ArrowUp`:e.preventDefault(),o(e=>e<=0?0:e-1);return;case`Home`:e.preventDefault(),o(0);return;case`End`:e.preventDefault(),o(n);return;case`Enter`:case` `:{e.preventDefault(),e.stopPropagation();let n=t[a];n&&(n.onAction(),f());return}default:return}}if(t.length===0)return null;let _=`${n}-menu`;return(0,v.jsxs)(`div`,{ref:s,className:`actions-menu`,children:[(0,v.jsx)(`button`,{ref:c,type:`button`,className:`actions-menu__trigger`,"data-testid":`actions-menu-trigger`,"aria-haspopup":`menu`,"aria-expanded":r,"aria-controls":r?_:void 0,"aria-label":h(`WeDevelopGrid.ActionsMenu.TRIGGER_LABEL`,`Actions`),onClick:p,children:(0,v.jsx)(`span`,{className:`actions-menu__dots`,"aria-hidden":`true`})}),r&&(0,v.jsx)(`div`,{id:_,ref:l,className:`actions-menu__dropdown`,role:`menu`,tabIndex:-1,"aria-activedescendant":d(a),"data-testid":`${n}-dropdown`,onKeyDown:g,children:t.map((e,t)=>(0,v.jsx)(`div`,{id:d(t),className:`actions-menu__item${e.destructive?` actions-menu__item--destructive`:``}`,role:`menuitem`,tabIndex:t===a?0:-1,onClick:t=>m(t,e.onAction),children:e.label},e.key))})]})}function Ed(e,t=!0){return at({queryKey:Aa.pages.search(e),queryFn:()=>ka(e||void 0),enabled:t})}function Dd(e){return at({queryKey:e===null?[`zones`,`disabled`]:Aa.zones.byPage(e),queryFn:e===null?ce:()=>Oa(e)})}function Od(e,t,n){let r=e!==null&&t!==null&&n!==null;return at({queryKey:r?Aa.acceptableContainers.byTarget(e,t,n):[`acceptableContainers`,`disabled`],queryFn:r?()=>Da(e,t,n):ce})}var kd={section:`page`,row:`section`,column:`row`,element:`column`};function Ad({isOpen:t,elementType:n,currentPageId:r,onConfirm:i,onCancel:a,error:o}){let s=(0,e.useRef)(null),[c,l]=(0,e.useState)(`page`),[u,d]=(0,e.useState)(``),[f,p]=(0,e.useState)(``),[m,g]=(0,e.useState)(r),[_,y]=(0,e.useState)(null),[b,x]=(0,e.useState)(null);(0,e.useEffect)(()=>{let e=setTimeout(()=>{p(u)},300);return()=>clearTimeout(e)},[u]),(0,e.useEffect)(()=>{t&&(l(`page`),d(``),p(``),g(r),y(null),x(null))},[t,r]),(0,e.useEffect)(()=>{let e=s.current;e!==null&&(t&&!e.open?e.showModal():!t&&e.open&&e.close())},[t]);let S=(0,e.useCallback)(()=>{a()},[a]),C=Ed(f,c===`page`),w=Dd(c===`page`?null:m),T=Od(c===`container`?m:null,c===`container`?_:null,c===`container`?n:null),E=(0,e.useCallback)(e=>{g(e)},[]),ee=(0,e.useCallback)(()=>{l(`zone`)},[]);(0,e.useEffect)(()=>{c===`zone`&&w.data!==void 0&&w.data.length===1&&(y(w.data[0]),l(n===`section`?`confirm`:`container`))},[c,w.data,n]);let D=(0,e.useCallback)(e=>{y(e)},[]),O=(0,e.useCallback)(()=>{_!==null&&l(n===`section`?`confirm`:`container`)},[n,_]),k=(0,e.useCallback)(e=>{x(e)},[]),A=(0,e.useCallback)(()=>{_!==null&&(c===`confirm`?i(m,_,{type:`page`,id:m}):b!==null&&i(m,_,{type:kd[n]??`column`,id:b}))},[i,m,_,b,c,n]),te=(0,e.useCallback)(()=>{c===`zone`?(l(`page`),y(null)):c===`container`?(l(`zone`),x(null)):c===`confirm`&&(y(null),l(w.data!==void 0&&w.data.length===1?`page`:`zone`))},[c,w.data]);return(0,v.jsxs)(`dialog`,{ref:s,className:`duplicate-to-dialog`,"data-testid":`duplicate-to-dialog`,onClose:S,onClick:e=>{e.preventDefault(),e.stopPropagation()},children:[(0,v.jsx)(`div`,{className:`duplicate-to-dialog__header`,children:(0,v.jsxs)(`h3`,{className:`duplicate-to-dialog__title`,children:[c===`page`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_PAGE_TITLE`,`Select target page`),c===`zone`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_ZONE_TITLE`,`Select zone`),c===`container`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_CONTAINER_TITLE`,`Select container`),c===`confirm`&&h(`WeDevelopGrid.DuplicateToDialog.STEP_CONFIRM_TITLE`,`Confirm duplication`)]})}),(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__body`,children:[c===`page`&&(0,v.jsxs)(`div`,{"data-testid":`duplicate-to-step-page`,children:[(0,v.jsx)(`input`,{type:`text`,className:`duplicate-to-dialog__search`,"data-testid":`duplicate-to-search`,placeholder:h(`WeDevelopGrid.DuplicateToDialog.SEARCH_PLACEHOLDER`,`Search pages…`),value:u,onChange:e=>d(e.target.value)}),C.isLoading&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__loading`,children:h(`WeDevelopGrid.DuplicateToDialog.LOADING_PAGES`,`Loading pages…`)}),C.data!==void 0&&(0,v.jsx)(`div`,{className:`duplicate-to-dialog__list`,"data-testid":`duplicate-to-page-list`,role:`listbox`,children:C.data.map(e=>(0,v.jsx)(`div`,{className:`duplicate-to-dialog__item${e.id===m?` duplicate-to-dialog__item--selected`:``}${e.hasGridZones?``:` duplicate-to-dialog__item--disabled`}`,role:`option`,"aria-selected":e.id===m,"aria-disabled":!e.hasGridZones,"data-testid":`duplicate-to-page-item`,onClick:e.hasGridZones?()=>E(e.id):void 0,onKeyDown:e.hasGridZones?t=>{(t.key===`Enter`||t.key===` `)&&E(e.id)}:void 0,tabIndex:e.hasGridZones?0:-1,children:e.title},e.id))})]}),c===`zone`&&(0,v.jsxs)(`div`,{"data-testid":`duplicate-to-step-zone`,children:[w.isLoading&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__loading`,children:h(`WeDevelopGrid.DuplicateToDialog.LOADING_ZONES`,`Loading zones…`)}),w.data!==void 0&&w.data.length>1&&(0,v.jsx)(`div`,{className:`duplicate-to-dialog__list`,"data-testid":`duplicate-to-zone-list`,role:`listbox`,children:w.data.map(e=>(0,v.jsx)(`div`,{className:`duplicate-to-dialog__item${e===_?` duplicate-to-dialog__item--selected`:``}`,role:`option`,"aria-selected":e===_,"data-testid":`duplicate-to-zone-item`,onClick:()=>D(e),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&D(e)},tabIndex:0,children:e},e))})]}),c===`container`&&(0,v.jsxs)(`div`,{"data-testid":`duplicate-to-step-container`,children:[T.isLoading&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__loading`,children:h(`WeDevelopGrid.DuplicateToDialog.LOADING_CONTAINERS`,`Loading containers…`)}),T.data!==void 0&&T.data.length===0&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__empty`,"data-testid":`duplicate-to-no-containers`,children:h(`WeDevelopGrid.DuplicateToDialog.NO_CONTAINERS`,`No compatible containers found in this zone`)}),T.data!==void 0&&T.data.length>0&&(0,v.jsx)(`div`,{className:`duplicate-to-dialog__list`,"data-testid":`duplicate-to-container-list`,role:`listbox`,children:T.data.map(e=>(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__item${e.id===b?` duplicate-to-dialog__item--selected`:``}`,role:`option`,"aria-selected":e.id===b,"data-testid":`duplicate-to-container-item`,onClick:()=>k(e.id),onKeyDown:t=>{(t.key===`Enter`||t.key===` `)&&k(e.id)},tabIndex:0,children:[(0,v.jsx)(`span`,{className:`duplicate-to-dialog__item-title`,children:e.title}),(0,v.jsx)(`span`,{className:`duplicate-to-dialog__item-type`,children:e.type})]},e.id))})]}),c===`confirm`&&(0,v.jsx)(`div`,{"data-testid":`duplicate-to-step-confirm`,children:(0,v.jsxs)(`p`,{className:`duplicate-to-dialog__summary`,children:[h(`WeDevelopGrid.DuplicateToDialog.CONFIRM_SUMMARY_PREFIX`,`Duplicate section to zone`),` `,(0,v.jsx)(`strong`,{children:_}),`?`]})})]}),(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__footer`,children:[o!=null&&(0,v.jsx)(`p`,{className:`duplicate-to-dialog__error`,"data-testid":`duplicate-to-error`,children:o}),(0,v.jsxs)(`div`,{className:`duplicate-to-dialog__actions`,children:[c!==`page`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--back`,"data-testid":`duplicate-to-back`,onClick:te,children:h(`WeDevelopGrid.DuplicateToDialog.BACK_BUTTON`,`Back`)}),(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--cancel`,onClick:S,children:h(`WeDevelopGrid.DuplicateToDialog.CANCEL_BUTTON`,`Cancel`)}),c===`page`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--next`,"data-testid":`duplicate-to-next`,disabled:m===0,onClick:ee,children:h(`WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON`,`Next`)}),c===`zone`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--next`,"data-testid":`duplicate-to-next`,disabled:_===null,onClick:O,children:h(`WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON`,`Next`)}),c===`container`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--confirm`,"data-testid":`duplicate-to-confirm`,disabled:b===null,onClick:A,children:h(`WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON`,`Confirm`)}),c===`confirm`&&(0,v.jsx)(`button`,{type:`button`,className:`duplicate-to-dialog__button duplicate-to-dialog__button--confirm`,"data-testid":`duplicate-to-confirm`,onClick:A,children:h(`WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON`,`Confirm`)})]})]})]})}function jd({node:e}){let{pageId:t}=Ro(),{action:n,dialog:r}=xd(e),{action:i}=Sd(e),{action:a,dialog:o}=wd(e);return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(Td,{actions:[...i===null?[]:[i],...a===null?[]:[a],...n===null?[]:[n]]}),r?.isOpen&&(0,v.jsx)(ud,{isOpen:r.isOpen,title:r.title,message:r.message,confirmLabel:h(`WeDevelopGrid.ElementActions.ARCHIVE_CONFIRM_LABEL`,`Archive`),onConfirm:r.onConfirm,onCancel:r.onCancel,destructive:!0}),o?.isOpen&&(0,v.jsx)(Ad,{isOpen:o.isOpen,elementType:o.elementType,currentPageId:t,onConfirm:o.onConfirm,onCancel:o.onCancel,error:o.error})]})}function Md({label:t,options:n,selectedValue:r,disabled:i,testId:a,onSelect:o}){let[s,c]=(0,e.useState)(!1),[l,u]=(0,e.useState)(0),d=(0,e.useRef)(null),f=(0,e.useRef)(null),p=(0,e.useRef)(null),m=`${a}-listbox`,h=(0,e.useId)(),g=(0,e.useCallback)(e=>`${h}opt-${e}`,[h]),_=(0,e.useCallback)(()=>c(!1),[]);(0,e.useEffect)(()=>{if(!s)return;let e=n.findIndex(e=>e.value===r);u(e>=0?e:0),p.current?.focus()},[s,n,r]),(0,e.useEffect)(()=>{if(!s)return;function e(e){d.current&&!d.current.contains(e.target)&&_()}return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[s,_]),(0,e.useEffect)(()=>{if(!s)return;function e(e){e.key===`Escape`&&(e.stopPropagation(),_(),f.current?.focus())}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[s,_]);function y(e){e.stopPropagation(),i||c(e=>!e)}function b(e){o(e),_()}function x(e){let t=n.length-1;switch(e.key){case`ArrowDown`:e.preventDefault(),u(e=>e>=t?t:e+1);return;case`ArrowUp`:e.preventDefault(),u(e=>e<=0?0:e-1);return;case`Home`:e.preventDefault(),u(0);return;case`End`:e.preventDefault(),u(t);return;case`Enter`:case` `:{e.preventDefault();let t=n[l];t&&b(t.value);return}default:return}}return(0,v.jsxs)(`div`,{ref:d,className:`grid-settings-picker`,children:[(0,v.jsx)(`button`,{ref:f,type:`button`,className:`grid-settings-picker__trigger${i?` grid-settings-picker__trigger--disabled`:``}`,"data-testid":a,"aria-haspopup":`listbox`,"aria-expanded":s,"aria-controls":s?m:void 0,disabled:i,onClick:y,children:t}),s&&(0,v.jsx)(`div`,{id:m,ref:p,className:`grid-settings-picker__options`,role:`listbox`,tabIndex:-1,"aria-activedescendant":g(l),"data-testid":`${a}-listbox`,onKeyDown:x,children:n.map((e,t)=>(0,v.jsx)(`div`,{id:g(t),className:`grid-settings-picker__option${e.value===r?` grid-settings-picker__option--selected`:``}${e.value===`hidden`?` grid-settings-picker__option--separator`:``}`,role:`option`,"aria-selected":e.value===r,tabIndex:t===l?0:-1,onClick:()=>b(e.value),children:e.label},e.value))})]})}function Nd({element:e}){return Vo()?(0,v.jsx)(Fd,{element:e}):(0,v.jsx)(Pd,{element:e})}function Pd({element:e}){let{attributes:t,listeners:n,setNodeRef:r,transform:i,transition:a,isDragging:o}=Wi({id:e.nodeKey}),s=pd(e),c=fd(e.statusFlags),l=e.editLink,u=l!==null,d=hd(i,a,o),f=[`element-card`,`element-card--${c}`,...u?[`element-card--clickable`]:[]].join(` `),p=(0,v.jsxs)(`div`,{className:`element-card__header`,children:[(0,v.jsx)(_d,{listeners:n,attributes:t,label:h(`WeDevelopGrid.ElementCard.MOVE_LABEL`,`Move {title}`,{title:e.title})}),(0,v.jsx)(`i`,{className:`element-card__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h4`,{className:`element-card__title`,"data-testid":`element-card-title`,children:e.title}),(0,v.jsx)(jd,{node:e})]});if(u){let e=e=>{if(o){e.preventDefault();return}if(!(e.target instanceof Element))return;let t=e.target.closest(`button, input, select, textarea, [role="button"], [role="menuitem"], [role="listbox"], [role="dialog"]`);t!==null&&e.currentTarget.contains(t)&&e.preventDefault()};return(0,v.jsx)(`a`,{ref:r,href:l,style:d,className:f,"data-testid":`element-card`,...s,onClick:e,children:p})}return(0,v.jsx)(`div`,{ref:r,style:d,className:f,"data-testid":`element-card`,...s,children:p})}function Fd({element:e}){return(0,v.jsx)(`div`,{className:`element-card element-card--${fd(e.statusFlags)}`,"data-testid":`element-card`,...pd(e),children:(0,v.jsxs)(`div`,{className:`element-card__header`,children:[(0,v.jsx)(`i`,{className:`element-card__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h4`,{className:`element-card__title`,"data-testid":`element-card-title`,children:e.title})]})})}function Id({message:e,variant:t}){return(0,v.jsx)(`div`,{className:t===`centered`?`empty-state empty-state--centered`:`empty-state`,children:e})}function Ld({allowedTypes:t,isOpen:n,onClose:r,onSelect:i}){let a=(0,e.useRef)(null);(0,e.useEffect)(()=>{let e=a.current;e!==null&&(n&&!e.open?e.showModal():!n&&e.open&&e.close())},[n]);let o=(0,e.useCallback)(()=>{r()},[r]),s=(0,e.useCallback)(e=>{i(e),r()},[i,r]),c=Object.entries(t);return(0,v.jsxs)(`dialog`,{ref:a,className:`element-type-picker`,"data-testid":`element-type-picker`,onClose:o,children:[(0,v.jsxs)(`div`,{className:`element-type-picker__header`,children:[(0,v.jsx)(`h3`,{className:`element-type-picker__title`,children:h(`WeDevelopGrid.ElementTypePicker.TITLE`,`Add content element`)}),(0,v.jsx)(`button`,{type:`button`,className:`element-type-picker__close`,"data-testid":`element-type-picker-close`,onClick:o,"aria-label":h(`WeDevelopGrid.ElementTypePicker.CLOSE_LABEL`,`Close`),children:`×`})]}),(0,v.jsx)(`div`,{className:`element-type-picker__body`,children:c.length>0?(0,v.jsx)(`div`,{className:`element-type-picker__grid`,children:c.map(([e,t])=>(0,v.jsxs)(`button`,{type:`button`,className:`element-type-picker__tile`,"data-testid":`element-type-tile`,onClick:()=>s(e),children:[(0,v.jsx)(`span`,{className:`element-type-picker__icon ${t.icon}`}),(0,v.jsx)(`span`,{className:`element-type-picker__label`,children:t.label}),t.description!==``&&(0,v.jsx)(`span`,{className:`element-type-picker__description`,children:t.description})]},e))}):(0,v.jsx)(`p`,{className:`element-type-picker__empty`,children:h(`WeDevelopGrid.ElementTypePicker.EMPTY_MESSAGE`,`No content element types available`)})})]})}function Rd(t){let{isCollapsed:n,toggle:r}=So();return{isCollapsed:n(t.nodeKey),onToggle:(0,e.useCallback)(()=>r(t.nodeKey),[r,t.nodeKey])}}function zd(t){return(0,e.useMemo)(()=>t.children?.map(e=>e.nodeKey)??[],[t.children])}function Bd({column:e}){return Vo()?(0,v.jsx)(Ud,{column:e}):(0,v.jsx)(Hd,{column:e})}function Vd(e,t){let n=Oo();return ko()===`margin`?{...t,"--col-width":`${e.width/n*100}%`,...e.offset>0?{"--col-offset":`${e.offset/n*100}%`}:{}}:{...t,"--col-span":String(e.width),...e.offset>0?{"--col-start":String(e.offset+1)}:{}}}function Hd({column:t}){let{activeViewport:n}=Fo(),{pageId:r,zone:i}=Ro(),a=Oo(),o=Mo(t.gridSettings,n),s=fd(t.statusFlags),{isCollapsed:c,onToggle:l}=Rd(t),{activeType:u}=no(),d=pd(t),f=mo(r,i),p=lo(r,i),[m,g]=(0,e.useState)(!1),{attributes:_,listeners:y,setNodeRef:b,transform:x,transition:S,isDragging:C,isOver:w}=Wi({id:t.nodeKey}),T=zd(t),E=w&&u===`column`,ee=u!==null||f.isPending,D=gd(`column-block`,s,{hidden:!o.visible,collapsed:c,"drop-target":E}),O=Vd(o,hd(x,S,C)),k=Ao(),A=jo(o.width),te=o.visible?`${o.width}/${a}`:`hidden`,ne=o.visible?o.width:`hidden`,j=o.offset===0?`none`:`+${o.offset}`,M=ee||o.width===a||!o.visible,N=(0,e.useCallback)(e=>{f.mutate({id:t.id,viewport:n,...o,...e})},[t.id,n,o,f]),re=(0,e.useCallback)(e=>{N(e===`hidden`?{visible:!1}:{width:e,visible:!0,offset:Math.min(o.offset,a-e)})},[N,a,o.offset]),P=(0,e.useCallback)(e=>{typeof e==`number`&&N({offset:e})},[N]),F=(0,e.useCallback)(()=>{g(!0)},[]),ie=(0,e.useCallback)(()=>{g(!1)},[]),ae=(0,e.useCallback)(e=>{p.mutate({className:e,parentId:t.id})},[p,t.id]),I=t.children??[],oe=t.allowedTypes??{},L=I.length>0,R=Object.keys(oe).length>0;return(0,v.jsxs)(`div`,{ref:b,style:O,className:`row-block__column`,...d,children:[(0,v.jsxs)(`div`,{className:D,"data-testid":`column-block`,children:[(0,v.jsxs)(`div`,{className:`column-block__header`,"data-testid":`column-header`,children:[(0,v.jsx)(_d,{listeners:y,attributes:_,label:h(`WeDevelopGrid.ColumnBlock.MOVE_LABEL`,`Move {title}`,{title:t.title})}),(0,v.jsx)(vd,{isCollapsed:c,onToggle:l,label:t.title}),(0,v.jsx)(`i`,{className:`column-block__icon ${t.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`column-block__title`,"data-testid":`column-title`,children:t.editLink===null?t.title:(0,v.jsx)(`a`,{href:t.editLink,"data-testid":`column-edit-link`,children:t.title})}),(0,v.jsxs)(`div`,{className:`column-block__header-meta`,children:[(0,v.jsx)(Md,{label:te,options:k,selectedValue:ne,disabled:ee,testId:`column-badge`,onSelect:re}),(0,v.jsx)(Md,{label:j,options:A,selectedValue:o.offset,disabled:M,testId:`column-offset-badge`,onSelect:P}),(0,v.jsx)(jd,{node:t})]})]}),(0,v.jsxs)(`div`,{className:`column-block__body`,children:[(0,v.jsx)(Ii,{items:T,strategy:Mi,children:L?I.map(e=>(0,v.jsx)(Nd,{element:e},e.nodeKey)):!R&&(0,v.jsx)(Id,{message:h(`WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS`,`No content blocks`)})}),R&&(0,v.jsx)(`button`,{type:`button`,className:`column-block__add-button`,"data-testid":`add-content-button`,onClick:F,children:h(`WeDevelopGrid.ColumnBlock.ADD_CONTENT_BUTTON`,`+ Add content`)})]})]}),R&&(0,v.jsx)(Ld,{allowedTypes:oe,isOpen:m,onClose:ie,onSelect:ae})]})}function Ud({column:e}){let{activeViewport:t}=Fo(),n=Mo(e.gridSettings,t),r=fd(e.statusFlags),{isCollapsed:i,onToggle:a}=Rd(e),o=pd(e),s=gd(`column-block`,r,{hidden:!n.visible,collapsed:i}),c=Vd(n,{}),l=e.children??[];return(0,v.jsx)(`div`,{style:c,className:`row-block__column`,...o,children:(0,v.jsxs)(`div`,{className:s,"data-testid":`column-block`,children:[(0,v.jsxs)(`div`,{className:`column-block__header`,"data-testid":`column-header`,children:[(0,v.jsx)(vd,{isCollapsed:i,onToggle:a,label:e.title}),(0,v.jsx)(`i`,{className:`column-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`column-block__title`,"data-testid":`column-title`,children:e.title})]}),(0,v.jsx)(`div`,{className:`column-block__body`,children:l.length>0?l.map(e=>(0,v.jsx)(Nd,{element:e},e.nodeKey)):(0,v.jsx)(Id,{message:h(`WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS`,`No content blocks`)})})]})})}var Wd={section:`page`,row:`section`,column:`row`};function Gd({parentId:e,childType:t,childLabel:n,variant:r}){let{pageId:i,zone:a}=Ro(),{mutate:o,isPending:s}=co(i,a);function c(){o({containerType:t,parent:{type:Wd[t],id:e},...t===`section`?{zone:a}:{}})}let l=(0,v.jsx)(`button`,{type:`button`,className:`add-child-button__button`,"data-testid":`add-child-button`,disabled:s,onClick:c,children:s?h(`WeDevelopGrid.AddChildButton.ADDING_LABEL`,`Adding {childLabel}…`,{childLabel:n}):h(`WeDevelopGrid.AddChildButton.ADD_LABEL`,`Add {childLabel}`,{childLabel:n})});return r===`empty-state`?(0,v.jsxs)(`div`,{className:`add-child-button add-child-button--empty-state`,"data-testid":`add-child-empty`,children:[(0,v.jsx)(`p`,{className:`add-child-button__message`,children:h(`WeDevelopGrid.AddChildButton.EMPTY_MESSAGE`,`No {childLabel}s yet`,{childLabel:n.toLowerCase()})}),l]}):(0,v.jsx)(`div`,{className:`add-child-button add-child-button--append`,"data-testid":`add-child-append`,children:l})}function Kd({row:e}){return Vo()?(0,v.jsx)(Xd,{row:e}):(0,v.jsx)(Yd,{row:e})}function qd(t){let{isCollapsed:n,toggle:r}=So();return{isCollapsed:n(t.nodeKey),onToggle:(0,e.useCallback)(()=>r(t.nodeKey),[r,t.nodeKey])}}function Jd(t){return(0,e.useMemo)(()=>t.children?.map(e=>e.nodeKey)??[],[t.children])}function Yd({row:e}){let t=ko()===`margin`?`flex`:`grid`,n=fd(e.statusFlags),{isCollapsed:r,onToggle:i}=qd(e),{activeType:a}=no(),o=pd(e),{attributes:s,listeners:c,setNodeRef:l,transform:u,transition:d,isDragging:f,isOver:p}=Wi({id:e.nodeKey}),m=gd(`row-block`,n,{collapsed:r,"drop-target":p&&a===`row`}),g=hd(u,d,f),_=Jd(e);return(0,v.jsxs)(`div`,{ref:l,style:g,className:m,"data-testid":`row-block`,...o,children:[(0,v.jsxs)(`div`,{className:`row-block__header`,"data-testid":`row-header`,children:[(0,v.jsx)(_d,{listeners:c,attributes:s,label:h(`WeDevelopGrid.RowBlock.MOVE_LABEL`,`Move {title}`,{title:e.title})}),(0,v.jsx)(vd,{isCollapsed:r,onToggle:i,label:e.title}),(0,v.jsx)(`i`,{className:`row-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h3`,{className:`row-block__title`,"data-testid":`row-title`,children:e.editLink===null?e.title:(0,v.jsx)(`a`,{href:e.editLink,"data-testid":`row-edit-link`,children:e.title})}),(0,v.jsx)(jd,{node:e})]}),(0,v.jsx)(`div`,{className:`row-block__columns row-block__columns--${t}`,style:t===`grid`?{"--grid-columns":String(Oo())}:void 0,children:(0,v.jsx)(Ii,{items:_,strategy:Oi,children:e.children!==null&&e.children.length>0?(0,v.jsxs)(v.Fragment,{children:[e.children.map(e=>(0,v.jsx)(Bd,{column:e},e.nodeKey)),(0,v.jsx)(Gd,{parentId:e.id,childType:`column`,childLabel:`Column`,variant:`append`})]}):(0,v.jsx)(Gd,{parentId:e.id,childType:`column`,childLabel:`Column`,variant:`empty-state`})})})]})}function Xd({row:e}){let t=ko()===`margin`?`flex`:`grid`,n=fd(e.statusFlags),{isCollapsed:r,onToggle:i}=qd(e),a=pd(e);return(0,v.jsxs)(`div`,{className:gd(`row-block`,n,{collapsed:r}),"data-testid":`row-block`,...a,children:[(0,v.jsxs)(`div`,{className:`row-block__header`,"data-testid":`row-header`,children:[(0,v.jsx)(vd,{isCollapsed:r,onToggle:i,label:e.title}),(0,v.jsx)(`i`,{className:`row-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h3`,{className:`row-block__title`,"data-testid":`row-title`,children:e.title})]}),(0,v.jsx)(`div`,{className:`row-block__columns row-block__columns--${t}`,style:t===`grid`?{"--grid-columns":String(Oo())}:void 0,children:e.children?.map(e=>(0,v.jsx)(Bd,{column:e},e.nodeKey))})]})}function Zd({section:e}){return Vo()?(0,v.jsx)(tf,{section:e}):(0,v.jsx)(ef,{section:e})}function Qd(t){let{isCollapsed:n,toggle:r}=So();return{isCollapsed:n(t.nodeKey),onToggle:(0,e.useCallback)(()=>r(t.nodeKey),[r,t.nodeKey])}}function $d(t){return(0,e.useMemo)(()=>t.children?.map(e=>e.nodeKey)??[],[t.children])}function ef({section:e}){let t=fd(e.statusFlags),{isCollapsed:n,onToggle:r}=Qd(e),{activeType:i}=no(),a=pd(e),{attributes:o,listeners:s,setNodeRef:c,transform:l,transition:u,isDragging:d,isOver:f}=Wi({id:e.nodeKey}),p=gd(`section-block`,t,{collapsed:n,"drop-target":f&&i===`section`}),m=hd(l,u,d),g=$d(e);return(0,v.jsxs)(`section`,{ref:c,style:m,className:p,"data-testid":`section-block`,...a,children:[(0,v.jsxs)(`div`,{className:`section-block__header`,"data-testid":`section-header`,children:[(0,v.jsx)(_d,{listeners:s,attributes:o,label:h(`WeDevelopGrid.SectionBlock.MOVE_LABEL`,`Move {title}`,{title:e.title})}),(0,v.jsx)(vd,{isCollapsed:n,onToggle:r,label:e.title}),(0,v.jsx)(`i`,{className:`section-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h2`,{className:`section-block__title`,"data-testid":`section-title`,children:e.editLink===null?e.title:(0,v.jsx)(`a`,{href:e.editLink,"data-testid":`section-edit-link`,children:e.title})}),(0,v.jsx)(jd,{node:e})]}),(0,v.jsx)(`div`,{className:`section-block__body`,children:(0,v.jsx)(Ii,{items:g,strategy:Mi,children:e.children!==null&&e.children.length>0?(0,v.jsxs)(v.Fragment,{children:[e.children.map(e=>(0,v.jsx)(Kd,{row:e},e.nodeKey)),(0,v.jsx)(Gd,{parentId:e.id,childType:`row`,childLabel:`Row`,variant:`append`})]}):(0,v.jsx)(Gd,{parentId:e.id,childType:`row`,childLabel:`Row`,variant:`empty-state`})})})]})}function tf({section:e}){let t=fd(e.statusFlags),{isCollapsed:n,onToggle:r}=Qd(e),i=pd(e);return(0,v.jsxs)(`section`,{className:gd(`section-block`,t,{collapsed:n}),"data-testid":`section-block`,...i,children:[(0,v.jsxs)(`div`,{className:`section-block__header`,"data-testid":`section-header`,children:[(0,v.jsx)(vd,{isCollapsed:n,onToggle:r,label:e.title}),(0,v.jsx)(`i`,{className:`section-block__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`h2`,{className:`section-block__title`,"data-testid":`section-title`,children:e.title})]}),(0,v.jsx)(`div`,{className:`section-block__body`,children:e.children?.map(e=>(0,v.jsx)(Kd,{row:e},e.nodeKey))})]})}function nf(e,t){return e===1?`${e} ${t}`:`${e} ${t}s`}function rf(e){return Ki(e)?e.children?.length??0:0}function af({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title}),(0,v.jsx)(`span`,{className:`drag-overlay-content__meta`,children:nf(rf(e),h(`WeDevelopGrid.DragOverlayContent.ROW_SINGULAR`,`row`))})]})}function of({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title}),(0,v.jsx)(`span`,{className:`drag-overlay-content__meta`,children:nf(rf(e),h(`WeDevelopGrid.DragOverlayContent.COLUMN_SINGULAR`,`column`))})]})}function sf({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title})]})}function cf({node:e,type:t}){return(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`i`,{className:`drag-overlay-content__icon ${e.blockSchema.icon}`}),(0,v.jsx)(`span`,{className:`drag-overlay-content__title`,"data-testid":`drag-overlay-${t}-title`,children:e.title})]})}var lf={section:af,row:of,column:sf,element:cf};function uf({node:e,type:t}){let n=lf[t];return(0,v.jsx)(`div`,{className:`drag-overlay-content drag-overlay-content--${t}`,"data-testid":`drag-overlay-${t}`,children:(0,v.jsx)(n,{node:e,type:t})})}function df(e){return{rootParent:{type:`page`,id:e??1},nodes:[],overrideCounts:{}}}function ff({pageId:t,zone:n,readonly:r=!1,version:i}){let{data:a,isLoading:o,error:s}=Ma(t,n,r?i:void 0),c=go(t??0,n),l=a??df(t),{dndContextProps:u,dragState:d,pendingTree:f}=ao({tree:l,onReorder:(e,t,n,r)=>{c.mutate({params:{element:e,parent:t,after:n},tree:l,clearPendingTree:r})}}),p=f??a,m=p===void 0?[]:p.nodes.filter(qi),g=bo(t??0),_=(0,e.useMemo)(()=>m.map(e=>e.nodeKey),[m]),y=(0,e.useMemo)(()=>({activeType:d?.activeType??null}),[d?.activeType]),b=r?`grid-editor grid-editor--readonly`:`grid-editor`,x=m.length>0,S=x?m.map(e=>(0,v.jsx)(Zd,{section:e},e.nodeKey)):r?(0,v.jsx)(`p`,{className:`grid-editor__empty-state`,children:h(`WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY`,`No sections in this version`)}):t!==null&&(0,v.jsx)(Gd,{parentId:t,childType:`section`,childLabel:`Section`,variant:`empty-state`});return(0,v.jsxs)(`div`,{className:b,"data-page-id":t??void 0,"data-zone":n,"data-testid":`grid-editor`,children:[o&&(0,v.jsx)(`p`,{className:`grid-editor__loading`,"data-testid":`grid-editor-loading`,children:h(`WeDevelopGrid.GridEditor.LOADING`,`Loading elements...`)}),s!==null&&(0,v.jsx)(`p`,{className:`grid-editor__error`,children:h(`WeDevelopGrid.GridEditor.LOAD_ERROR`,`Failed to load elements: {message}`,{message:s.message})}),a!==void 0&&t!==null&&(0,v.jsx)(Qo,{children:(0,v.jsx)(Lo,{value:{pageId:t,zone:n},children:(0,v.jsx)(Po,{children:(0,v.jsx)(Bo,{value:r,children:(0,v.jsxs)(xo.Provider,{value:g,children:[(0,v.jsxs)(`div`,{className:`grid-editor__header`,children:[(0,v.jsx)(dd,{}),(0,v.jsx)(cd,{})]}),(0,v.jsx)(Xu,{tree:l}),(0,v.jsx)(ad,{}),r?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(Qu,{tree:l}),S]}):(0,v.jsxs)(ti,{...u,measuring:{droppable:{strategy:_r.Always}},children:[(0,v.jsxs)(to.Provider,{value:y,children:[(0,v.jsx)(Qu,{tree:l}),(0,v.jsxs)(Ii,{items:_,strategy:Mi,children:[S,x&&(0,v.jsx)(Gd,{parentId:t,childType:`section`,childLabel:`Section`,variant:`append`})]})]}),(0,v.jsx)(xi,{children:d!==null&&(0,v.jsx)(uf,{node:d.activeNode,type:d.activeType})})]})]})})})})}),a!==void 0&&t===null&&(0,v.jsx)(Id,{message:h(`WeDevelopGrid.GridEditor.NO_SECTIONS`,`No sections yet`),variant:`centered`})]})}function pf({data:e,readOnly:t}){let n=typeof e?.pageId==`number`?e.pageId:null,r=typeof e?.zone==`string`?e.zone:`main`,i=typeof e?.version==`number`&&e.version>0?e.version:void 0,a=t===!0||e?.readonly===!0;return n===null?null:(0,v.jsx)(st,{children:(0,v.jsx)(y,{children:(0,v.jsx)(ff,{pageId:n,zone:r,readonly:a,version:i})})})}function mf(){ct().component.registerMany({GridEditor:ff,GridEditorField:pf})}document.addEventListener(`DOMContentLoaded`,()=>{try{mf()}catch(e){console.warn(`[GridEditor] Failed to register components.`,e)}})})(React,ReactDomClient,ReactDom); \ No newline at end of file diff --git a/client/dist/js/preview.js b/client/dist/js/preview.js new file mode 100644 index 00000000..70df45bf --- /dev/null +++ b/client/dist/js/preview.js @@ -0,0 +1 @@ +var GridInspectPreview=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`grid-inspect-breadcrumb`,n=32,r=8,i=8;function a(e){let n=document.querySelector(`.${t}`);return n===null&&(n=document.createElement(`div`),n.className=t,document.body.appendChild(n)),n.textContent=e.join(` › `),n}function o(e,t){let a=e.getBoundingClientRect(),o=t.top-n<0?t.bottom+r:t.top-n,s=t.left,c=window.innerWidth-a.width-i,l=Math.max(i,Math.min(s,c));e.style.position=`fixed`,e.style.top=`${o}px`,e.style.left=`${l}px`}function s(){let e=document.querySelector(`.${t}`);e!==null&&e.remove()}var c=`data-grid-element-id`,l=`data-grid-element-title`;function u(e){return e===null?null:(e instanceof HTMLElement?e:e.parentElement)?.closest(`[${c}]`)??null}function d(e){let t=e.getAttribute(c);if(t===null)return null;let n=Number.parseInt(t,10);return Number.isFinite(n)&&String(n)===t?n:null}function f(e){let t=e.getAttribute(l);return t!==null&&t!==``?t:null}function p(e){if(typeof e!=`object`||!e)return!1;let t=e;switch(t.type){case`grid-inspect:activate`:case`grid-inspect:deactivate`:case`grid-inspect:clear`:return!0;case`grid-inspect:highlight`:return typeof t.id==`number`&&Number.isFinite(t.id);default:return!1}}function m(e){window.parent.postMessage(e,window.location.origin)}function h(e,t){let n=n=>{n.origin===e&&p(n.data)&&t(n.data)};return window.addEventListener(`message`,n),()=>window.removeEventListener(`message`,n)}var g=`grid-inspect-target`,_=[`grid-inspect-ancestor-1`,`grid-inspect-ancestor-2`,`grid-inspect-ancestor-3`];function v(){let e={active:!1,mutated:new Set,currentHover:null,pendingFrame:null,pendingTarget:null,onMouseMove:()=>{},onMouseLeave:()=>{},destroy:()=>{T(e)},flush:()=>{e.pendingFrame!==null&&(cancelAnimationFrame(e.pendingFrame),y(e,e.pendingTarget),e.pendingFrame=null,e.pendingTarget=null)}};return e.onMouseMove=t=>{e.active&&(e.pendingTarget=u(t.target),e.pendingFrame===null&&(e.pendingFrame=requestAnimationFrame(()=>{e.pendingFrame=null,y(e,e.pendingTarget),e.pendingTarget=null})))},e.onMouseLeave=()=>{e.active&&e.currentHover!==null&&(C(e),m({type:`grid-inspect:unhover`}),e.currentHover=null)},e}function y(e,t){if(t===e.currentHover)return;if(C(e),t===null){m({type:`grid-inspect:unhover`}),e.currentHover=null;return}let n=d(t);n!==null&&(b(e,t),e.currentHover=t,m({type:`grid-inspect:hover`,id:n}))}function b(e,t){t.classList.add(g),e.mutated.add(t);let n=x(t).slice(0,3);n.forEach((t,n)=>{t.classList.add(_[n]),e.mutated.add(t)});let r=S(t,n),i=t.getBoundingClientRect();o(a(r),i)}function x(e){let t=[],n=e.parentElement;for(;n!==null;)n.hasAttribute(`data-grid-element-id`)&&t.push(n),n=n.parentElement;return t}function S(e,t){let n=[...t].reverse().map(e=>f(e)??`Element`);return n.push(f(e)??`Element`),n}function C(e){for(let t of e.mutated)t.classList.remove(g,..._);e.mutated.clear(),s()}function w(e){e.active||(e.active=!0,document.addEventListener(`mousemove`,e.onMouseMove,{passive:!0}),document.documentElement.addEventListener(`mouseleave`,e.onMouseLeave))}function T(e){e.active&&(e.active=!1,document.removeEventListener(`mousemove`,e.onMouseMove),document.documentElement.removeEventListener(`mouseleave`,e.onMouseLeave),e.pendingFrame!==null&&(cancelAnimationFrame(e.pendingFrame),e.pendingFrame=null,e.pendingTarget=null),C(e),e.currentHover=null)}function E(e,t){let n=document.querySelector(`[data-grid-element-id="${t}"]`);if(n===null){m({type:`grid-inspect:missing`,id:t});return}let r=n.getBoundingClientRect();if(r.width===0&&r.height===0){m({type:`grid-inspect:missing`,id:t});return}C(e),b(e,n),n.scrollIntoView({behavior:`smooth`,block:`nearest`})}function D(e){C(e),e.currentHover=null}typeof window<`u`&&window!==window.top&&O();function O(){let e=v(),t=h(window.location.origin,t=>{switch(m({type:`grid-inspect:ready`}),t.type){case`grid-inspect:activate`:w(e);return;case`grid-inspect:deactivate`:T(e);return;case`grid-inspect:highlight`:E(e,t.id);return;case`grid-inspect:clear`:D(e);return}});m({type:`grid-inspect:ready`}),window.addEventListener(`unload`,()=>{t(),e.destroy()})}return e.boot=O,e})({}); \ No newline at end of file diff --git a/client/dist/styles/bundle.css b/client/dist/styles/bundle.css index cb33e579..ea7bf030 100644 --- a/client/dist/styles/bundle.css +++ b/client/dist/styles/bundle.css @@ -1,2 +1,2 @@ -.duplicate-to-dialog{border:1px solid var(--border-color-light,#ccc);border-radius:4px;width:90vw;max-width:480px;padding:0}.duplicate-to-dialog::backdrop{background:#0006}.duplicate-to-dialog__header{border-bottom:1px solid var(--border-color-light,#eee);padding:12px 16px}.duplicate-to-dialog__title{margin:0;font-size:16px;font-weight:600}.duplicate-to-dialog__body{max-height:400px;padding:16px;overflow-y:auto}.duplicate-to-dialog__search{border:1px solid var(--border-color-light,#ccc);box-sizing:border-box;border-radius:4px;width:100%;margin-bottom:12px;padding:8px 12px;font-size:14px}.duplicate-to-dialog__loading{color:var(--text-color-muted,#555);margin:0;font-size:14px}.duplicate-to-dialog__empty{color:var(--text-color-muted,#555);margin:0;font-size:14px;font-style:italic}.duplicate-to-dialog__list{margin:0;padding:0;list-style:none}.duplicate-to-dialog__item{cursor:pointer;border-radius:4px;justify-content:space-between;align-items:center;padding:8px 12px;font-size:14px;display:flex}.duplicate-to-dialog__item:hover{background:#f5f5f5}.duplicate-to-dialog__item--selected{background:var(--color-primary-light,#e6f2ff);font-weight:500}.duplicate-to-dialog__item--disabled{opacity:.5;cursor:not-allowed}.duplicate-to-dialog__item--disabled:hover{background:0 0}.duplicate-to-dialog__item-type{color:var(--text-color-muted,#555);font-size:12px}.duplicate-to-dialog__footer{border-top:1px solid var(--border-color-light,#eee);padding:12px 16px}.duplicate-to-dialog__error{color:#c4183c;margin:0 0 8px;font-size:13px}.duplicate-to-dialog__actions{justify-content:flex-end;gap:8px;display:flex}.duplicate-to-dialog__button{border:1px solid var(--border-color-light,#ccc);cursor:pointer;border-radius:4px;padding:6px 16px;font-size:13px;font-weight:500;line-height:1.4}.duplicate-to-dialog__button--cancel,.duplicate-to-dialog__button--back{color:var(--text-color,#333);background:#fff}.duplicate-to-dialog__button--cancel:hover,.duplicate-to-dialog__button--back:hover{background:#f5f5f5}.duplicate-to-dialog__button--next,.duplicate-to-dialog__button--confirm{background:var(--color-primary,#0071c4);border-color:var(--color-primary,#0071c4);color:#fff}.duplicate-to-dialog__button--next:hover,.duplicate-to-dialog__button--confirm:hover{filter:brightness(.9)}.duplicate-to-dialog__button--next:disabled,.duplicate-to-dialog__button--confirm:disabled{opacity:.5;cursor:not-allowed}.grid-settings-picker{position:relative}.grid-settings-picker__trigger{cursor:pointer;background:#dee2e6;border:none;border-radius:6px;padding:1px 6px;font-size:.65em;font-weight:600;line-height:1.3;display:inline-block}.grid-settings-picker__trigger:hover:not(:disabled){filter:brightness(.95)}.grid-settings-picker__trigger--disabled,.grid-settings-picker__trigger:disabled{opacity:.5;cursor:not-allowed}.grid-settings-picker__options{z-index:100;background:#fff;border:1px solid #dee2e6;border-radius:6px;min-width:80px;max-height:200px;margin:4px 0 0;padding:4px 0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 4px 12px #0000001f}.grid-settings-picker__option{cursor:pointer;white-space:nowrap;padding:4px 12px;font-size:.75em}.grid-settings-picker__option:hover{background:#f0f4f8}.grid-settings-picker__option--selected{background:#f0f4f8;font-weight:600}.grid-settings-picker__option--separator{border-top:1px solid #dee2e6;margin-top:4px;padding-top:8px}.actions-menu{margin-left:auto;position:relative}.actions-menu__trigger{cursor:pointer;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:flex}.actions-menu__trigger:hover{background:#dee2e6}.actions-menu__dots{flex-direction:column;gap:2px;display:flex}.actions-menu__dots:before,.actions-menu__dots:after{content:"";background:#6c757d;border-radius:50%;width:3px;height:3px;display:block}.actions-menu__dots{background:#6c757d;border-radius:50%;width:3px;height:3px}.actions-menu__dropdown{z-index:100;background:#fff;border:1px solid #dee2e6;border-radius:6px;min-width:120px;margin:4px 0 0;padding:4px 0;list-style:none;position:absolute;top:100%;right:0;box-shadow:0 4px 12px #0000001f}.actions-menu__item{cursor:pointer;white-space:nowrap;padding:6px 12px;font-size:13px}.actions-menu__item:hover{background:#f0f4f8}.actions-menu__item--destructive{color:#c4183c}.actions-menu__item--destructive:hover{background:#fdf0f2}.add-child-button--empty-state{color:#6c757d;text-align:center;border:1px dashed #dee2e6;flex-direction:column;justify-content:center;align-items:center;gap:12px;min-height:120px;padding:16px;display:flex}.add-child-button--append{justify-content:center;padding:8px 0;display:flex}.add-child-button__message{margin:0}.add-child-button__button{cursor:pointer;color:#333;background:#fff;border:1px solid #dee2e6;border-radius:4px;padding:6px 16px;font-size:13px}.add-child-button__button:hover:not(:disabled){background:#f0f4f8;border-color:#c0c8d0}.add-child-button__button:disabled{cursor:not-allowed;opacity:.6}.collapse-toggle{cursor:pointer;background:0 0;border:none;border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;width:20px;height:20px;padding:0;display:inline-flex}.collapse-toggle:hover{background:#00000014}.collapse-toggle:focus-visible{outline-offset:1px;outline:2px solid #4a90d9}.collapse-toggle__chevron{border-top:4px solid #0000;border-bottom:4px solid #0000;border-left:5px solid #5a6a7a;width:0;height:0;transition:transform .15s;display:inline-block;transform:rotate(90deg)}.collapse-toggle--collapsed .collapse-toggle__chevron{transform:rotate(0)}.confirm-dialog{border:1px solid var(--border-color-light,#ccc);border-radius:4px;width:90vw;max-width:420px;padding:0}.confirm-dialog::backdrop{background:#0006}.confirm-dialog__header{border-bottom:1px solid var(--border-color-light,#eee);padding:12px 16px}.confirm-dialog__title{margin:0;font-size:16px;font-weight:600}.confirm-dialog__body{padding:16px}.confirm-dialog__message{color:var(--text-color-muted,#555);margin:0;font-size:14px;line-height:1.5}.confirm-dialog__footer{border-top:1px solid var(--border-color-light,#eee);justify-content:flex-end;gap:8px;padding:12px 16px;display:flex}.confirm-dialog__button{border:1px solid var(--border-color-light,#ccc);cursor:pointer;border-radius:4px;padding:6px 16px;font-size:13px;font-weight:500;line-height:1.4}.confirm-dialog__button--cancel{color:var(--text-color,#333);background:#fff}.confirm-dialog__button--cancel:hover{background:#f5f5f5}.confirm-dialog__button--confirm{background:var(--color-primary,#0071c4);border-color:var(--color-primary,#0071c4);color:#fff}.confirm-dialog__button--confirm:hover{filter:brightness(.9)}.confirm-dialog__button--destructive{background:#c4183c;border-color:#c4183c}.confirm-dialog__button--destructive:hover{filter:brightness(.9)}.column-block{border-left:3px solid #0000}.column-block--draft{border-left-color:#0071c4}.column-block--published{border-left-color:#3fa142}.column-block--modified{border-left-color:#d4a017}.column-block--removed{border-left-color:#c4001d}.column-block{background:#f8f9fa;min-height:60px;padding:8px;container:column-block/inline-size}.column-block--hidden{opacity:.4;position:relative}.column-block--hidden:before{content:"";pointer-events:none;background:repeating-linear-gradient(-45deg,#0000,#0000 4px,#0000000f 4px 8px);position:absolute;inset:0}.column-block__header{flex-wrap:wrap;align-items:center;gap:4px 6px;margin-bottom:8px;display:flex}.column-block__icon{color:#6c757d;flex:none;font-size:.75em}.column-block__title{color:#6c757d;text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;font-size:.75em;font-weight:600;overflow:hidden}.column-block__title a{color:inherit;cursor:pointer;text-decoration:none}.column-block__title a:hover{text-decoration:underline}.column-block__header-meta{flex-wrap:wrap;flex:0 auto;align-items:center;gap:6px;min-width:0;margin-left:auto;display:flex}@container column-block (width<=240px){.column-block .column-block__header-meta{flex-basis:100%;justify-content:flex-end;margin-left:0}}.column-block__body{flex-direction:column;gap:8px;display:flex}.column-block--collapsed .column-block__body{display:none}.column-block__add-button{color:#6c757d;cursor:pointer;background:0 0;border:1px dashed #6c757d;border-radius:4px;width:100%;padding:8px;font-size:13px;transition:border-color .15s,color .15s}.column-block__add-button:hover{color:#2563eb;border-color:#2563eb}.column-block--drop-target{background:#2563eb1a;outline:2px solid #2563eb}.column-block--drop-target .empty-state{border-color:#2563eb}.drag-handle{cursor:grab;touch-action:none;background:0 0;border:none;flex-shrink:0;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}.drag-handle:active{cursor:grabbing}.drag-handle__icon{opacity:.5;background-image:radial-gradient(circle,currentColor 1.5px,#0000 1.5px),radial-gradient(circle,currentColor 1.5px,#0000 1.5px);background-position:0 0,5px 0;background-size:5px 5px;width:10px;height:14px;display:block}.drag-handle:hover .drag-handle__icon{opacity:.8}.drag-overlay-content{opacity:.85;pointer-events:none;background:#fff;border-radius:4px;flex-wrap:wrap;align-items:baseline;gap:2px 4px;padding:8px 12px;display:flex;box-shadow:0 4px 12px #00000026}.drag-overlay-content__title{color:#333;font-size:13px;font-weight:600;line-height:1.3}.drag-overlay-content__meta{color:#6c757d;flex-basis:100%;font-size:12px}.drag-overlay-content__icon{color:#6c757d;font-size:12px}.element-card{border-left:3px solid #0000}.element-card--draft{border-left-color:#0071c4}.element-card--published{border-left-color:#3fa142}.element-card--modified{border-left-color:#d4a017}.element-card--removed{border-left-color:#c4001d}.element-card{background:#fff;border-radius:4px;padding:8px 12px;box-shadow:0 1px 3px #00000014}.element-card__header{align-items:baseline;gap:6px;display:flex}.element-card__title{margin:0;font-size:.75em;font-weight:600}.element-card__icon{color:#6c757d;font-size:.75em}.element-card--clickable{cursor:pointer}.element-card--clickable:hover{box-shadow:0 1px 6px #00000026}.element-card[style*=transform]{z-index:1}.element-type-picker{border:1px solid var(--border-color-light,#ccc);border-radius:4px;width:90vw;max-width:600px;padding:0}.element-type-picker::backdrop{background:#0006}.element-type-picker__header{border-bottom:1px solid var(--border-color-light,#eee);justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.element-type-picker__title{margin:0;font-size:16px;font-weight:600}.element-type-picker__close{cursor:pointer;color:var(--text-color-muted,#666);background:0 0;border:none;padding:0 4px;font-size:24px;line-height:1}.element-type-picker__body{padding:16px}.element-type-picker__grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;display:grid}.element-type-picker__tile{border:1px solid var(--border-color-light,#ddd);background:var(--bg-color-light,#fff);cursor:pointer;text-align:center;border-radius:4px;flex-direction:column;align-items:center;gap:6px;padding:16px 12px;transition:border-color .15s,box-shadow .15s;display:flex}.element-type-picker__tile:hover{border-color:var(--color-primary,#0071c4);box-shadow:0 0 0 1px var(--color-primary,#0071c4)}.element-type-picker__icon{color:var(--text-color-muted,#666);font-size:24px}.element-type-picker__label{font-size:13px;font-weight:600}.element-type-picker__description{color:var(--text-color-muted,#888);font-size:11px;line-height:1.3}.element-type-picker__empty{text-align:center;color:var(--text-color-muted,#888);margin:0;padding:24px 0}.empty-state{color:#6c757d;text-align:center;border:1px dashed #dee2e6;padding:16px}.empty-state--centered{justify-content:center;align-items:center;min-height:120px;display:flex}.row-block{background:#fff;border:1px solid #dee2e6;border-left:3px solid #0000;border-radius:4px;padding:12px}.row-block--draft{border-left-color:#0071c4}.row-block--published{border-left-color:#3fa142}.row-block--modified{border-left-color:#d4a017}.row-block--removed{border-left-color:#c4001d}.row-block__header{align-items:center;gap:6px;margin-bottom:8px;display:flex}.row-block__icon{color:#6c757d;font-size:.75em}.row-block__title{color:#6c757d;margin:0;font-size:.75em;font-weight:600}.row-block__title a{color:inherit;cursor:pointer;text-decoration:none}.row-block__title a:hover{text-decoration:underline}.row-block--collapsed>:not(.row-block__header){display:none}.row-block--drop-target{background:#2563eb1a;border-color:#2563eb}.row-block--drop-target .empty-state{border-color:#2563eb}.row-block[style*=transform]{z-index:1}.row-block__columns--flex{flex-wrap:wrap;row-gap:8px;display:flex}.row-block__columns--grid{grid-template-columns:repeat(var(--grid-columns,12), 1fr);gap:8px;display:grid}.row-block__column{box-sizing:border-box}.row-block__columns--flex>.row-block__column{flex:0 0 var(--col-width);max-width:var(--col-width);margin-left:var(--col-offset,0);padding:0 4px}.row-block__columns--grid>.row-block__column{grid-column:var(--col-start,auto)/span var(--col-span)}.row-block__columns--flex>.add-child-button{flex:0 0 100%;padding:0 4px}.row-block__columns--grid>.add-child-button{grid-column:1/-1}.section-block{background:#f0f4f8;border:2px solid #c0c8d0;border-left:3px solid #0000;border-radius:4px;width:100%;padding:16px}.section-block--draft{border-left-color:#0071c4}.section-block--published{border-left-color:#3fa142}.section-block--modified{border-left-color:#d4a017}.section-block--removed{border-left-color:#c4001d}.section-block__header{align-items:center;gap:6px;margin-bottom:12px;display:flex}.section-block__icon{color:#6c757d;font-size:.75em}.section-block__title{color:#3a4a5c;margin:0;font-size:.75em;font-weight:600}.section-block__title a{color:inherit;cursor:pointer;text-decoration:none}.section-block__title a:hover{text-decoration:underline}.section-block__body{flex-direction:column;gap:12px;display:flex}.section-block--collapsed .section-block__body{display:none}.section-block--drop-target{background:#2563eb1a;border-color:#2563eb}.section-block--drop-target .empty-state{border-color:#2563eb}.section-block[style*=transform]{z-index:1}.viewport-switcher{align-items:center;gap:12px;display:flex}.viewport-switcher [role=group]{border:1px solid #ccc;border-radius:4px;display:flex;overflow:hidden}.viewport-switcher__button{color:#333;cursor:pointer;background:#fff;border:none;padding:6px 16px;font-size:13px;line-height:1.4;transition:background-color .15s,color .15s}.viewport-switcher__button+.viewport-switcher__button{border-left:1px solid #ccc}.viewport-switcher__button:hover:not(.viewport-switcher__button--active){background-color:#f0f0f0}.viewport-switcher__button--active{color:#fff;background-color:#0071c4}.viewport-switcher__reset{color:#c44;cursor:pointer;background:0 0;border:1px solid #c44;border-radius:4px;padding:4px 12px;font-size:12px;line-height:1.4;transition:background-color .15s,color .15s}.viewport-switcher__reset:hover{color:#fff;background-color:#c44}.grid-editor-holder>.grid-editor__container{flex:0 0 100%;max-width:100%;margin-left:0}.grid-editor{position:relative} +.duplicate-to-dialog{border:1px solid var(--border-color-light,#ccc);border-radius:4px;width:90vw;max-width:480px;padding:0}.duplicate-to-dialog::backdrop{background:#0006}.duplicate-to-dialog__header{border-bottom:1px solid var(--border-color-light,#eee);padding:12px 16px}.duplicate-to-dialog__title{margin:0;font-size:16px;font-weight:600}.duplicate-to-dialog__body{max-height:400px;padding:16px;overflow-y:auto}.duplicate-to-dialog__search{border:1px solid var(--border-color-light,#ccc);box-sizing:border-box;border-radius:4px;width:100%;margin-bottom:12px;padding:8px 12px;font-size:14px}.duplicate-to-dialog__loading{color:var(--text-color-muted,#555);margin:0;font-size:14px}.duplicate-to-dialog__empty{color:var(--text-color-muted,#555);margin:0;font-size:14px;font-style:italic}.duplicate-to-dialog__list{margin:0;padding:0;list-style:none}.duplicate-to-dialog__item{cursor:pointer;border-radius:4px;justify-content:space-between;align-items:center;padding:8px 12px;font-size:14px;display:flex}.duplicate-to-dialog__item:hover{background:#f5f5f5}.duplicate-to-dialog__item--selected{background:var(--color-primary-light,#e6f2ff);font-weight:500}.duplicate-to-dialog__item--disabled{opacity:.5;cursor:not-allowed}.duplicate-to-dialog__item--disabled:hover{background:0 0}.duplicate-to-dialog__item-type{color:var(--text-color-muted,#555);font-size:12px}.duplicate-to-dialog__footer{border-top:1px solid var(--border-color-light,#eee);padding:12px 16px}.duplicate-to-dialog__error{color:#c4183c;margin:0 0 8px;font-size:13px}.duplicate-to-dialog__actions{justify-content:flex-end;gap:8px;display:flex}.duplicate-to-dialog__button{border:1px solid var(--border-color-light,#ccc);cursor:pointer;border-radius:4px;padding:6px 16px;font-size:13px;font-weight:500;line-height:1.4}.duplicate-to-dialog__button--cancel,.duplicate-to-dialog__button--back{color:var(--text-color,#333);background:#fff}.duplicate-to-dialog__button--cancel:hover,.duplicate-to-dialog__button--back:hover{background:#f5f5f5}.duplicate-to-dialog__button--next,.duplicate-to-dialog__button--confirm{background:var(--color-primary,#0071c4);border-color:var(--color-primary,#0071c4);color:#fff}.duplicate-to-dialog__button--next:hover,.duplicate-to-dialog__button--confirm:hover{filter:brightness(.9)}.duplicate-to-dialog__button--next:disabled,.duplicate-to-dialog__button--confirm:disabled{opacity:.5;cursor:not-allowed}.grid-settings-picker{position:relative}.grid-settings-picker__trigger{cursor:pointer;background:#dee2e6;border:none;border-radius:6px;padding:1px 6px;font-size:.65em;font-weight:600;line-height:1.3;display:inline-block}.grid-settings-picker__trigger:hover:not(:disabled){filter:brightness(.95)}.grid-settings-picker__trigger--disabled,.grid-settings-picker__trigger:disabled{opacity:.5;cursor:not-allowed}.grid-settings-picker__options{z-index:100;background:#fff;border:1px solid #dee2e6;border-radius:6px;min-width:80px;max-height:200px;margin:4px 0 0;padding:4px 0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 4px 12px #0000001f}.grid-settings-picker__option{cursor:pointer;white-space:nowrap;padding:4px 12px;font-size:.75em}.grid-settings-picker__option:hover{background:#f0f4f8}.grid-settings-picker__option--selected{background:#f0f4f8;font-weight:600}.grid-settings-picker__option--separator{border-top:1px solid #dee2e6;margin-top:4px;padding-top:8px}.actions-menu{margin-left:auto;position:relative}.actions-menu__trigger{cursor:pointer;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:flex}.actions-menu__trigger:hover{background:#dee2e6}.actions-menu__dots{flex-direction:column;gap:2px;display:flex}.actions-menu__dots:before,.actions-menu__dots:after{content:"";background:#6c757d;border-radius:50%;width:3px;height:3px;display:block}.actions-menu__dots{background:#6c757d;border-radius:50%;width:3px;height:3px}.actions-menu__dropdown{z-index:100;background:#fff;border:1px solid #dee2e6;border-radius:6px;min-width:120px;margin:4px 0 0;padding:4px 0;list-style:none;position:absolute;top:100%;right:0;box-shadow:0 4px 12px #0000001f}.actions-menu__item{cursor:pointer;white-space:nowrap;padding:6px 12px;font-size:13px}.actions-menu__item:hover{background:#f0f4f8}.actions-menu__item--destructive{color:#c4183c}.actions-menu__item--destructive:hover{background:#fdf0f2}.add-child-button--empty-state{color:#6c757d;text-align:center;border:1px dashed #dee2e6;flex-direction:column;justify-content:center;align-items:center;gap:12px;min-height:120px;padding:16px;display:flex}.add-child-button--append{justify-content:center;padding:8px 0;display:flex}.add-child-button__message{margin:0}.add-child-button__button{cursor:pointer;color:#333;background:#fff;border:1px solid #dee2e6;border-radius:4px;padding:6px 16px;font-size:13px}.add-child-button__button:hover:not(:disabled){background:#f0f4f8;border-color:#c0c8d0}.add-child-button__button:disabled{cursor:not-allowed;opacity:.6}.collapse-toggle{cursor:pointer;background:0 0;border:none;border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;width:20px;height:20px;padding:0;display:inline-flex}.collapse-toggle:hover{background:#00000014}.collapse-toggle:focus-visible{outline-offset:1px;outline:2px solid #4a90d9}.collapse-toggle__chevron{border-top:4px solid #0000;border-bottom:4px solid #0000;border-left:5px solid #5a6a7a;width:0;height:0;transition:transform .15s;display:inline-block;transform:rotate(90deg)}.collapse-toggle--collapsed .collapse-toggle__chevron{transform:rotate(0)}.confirm-dialog{border:1px solid var(--border-color-light,#ccc);border-radius:4px;width:90vw;max-width:420px;padding:0}.confirm-dialog::backdrop{background:#0006}.confirm-dialog__header{border-bottom:1px solid var(--border-color-light,#eee);padding:12px 16px}.confirm-dialog__title{margin:0;font-size:16px;font-weight:600}.confirm-dialog__body{padding:16px}.confirm-dialog__message{color:var(--text-color-muted,#555);margin:0;font-size:14px;line-height:1.5}.confirm-dialog__footer{border-top:1px solid var(--border-color-light,#eee);justify-content:flex-end;gap:8px;padding:12px 16px;display:flex}.confirm-dialog__button{border:1px solid var(--border-color-light,#ccc);cursor:pointer;border-radius:4px;padding:6px 16px;font-size:13px;font-weight:500;line-height:1.4}.confirm-dialog__button--cancel{color:var(--text-color,#333);background:#fff}.confirm-dialog__button--cancel:hover{background:#f5f5f5}.confirm-dialog__button--confirm{background:var(--color-primary,#0071c4);border-color:var(--color-primary,#0071c4);color:#fff}.confirm-dialog__button--confirm:hover{filter:brightness(.9)}.confirm-dialog__button--destructive{background:#c4183c;border-color:#c4183c}.confirm-dialog__button--destructive:hover{filter:brightness(.9)}.column-block{border-left:3px solid #0000}.column-block--draft{border-left-color:#0071c4}.column-block--published{border-left-color:#3fa142}.column-block--modified{border-left-color:#d4a017}.column-block--removed{border-left-color:#c4001d}.column-block{background:#f8f9fa;min-height:60px;padding:8px;container:column-block/inline-size}.column-block--hidden{opacity:.4;position:relative}.column-block--hidden:before{content:"";pointer-events:none;background:repeating-linear-gradient(-45deg,#0000,#0000 4px,#0000000f 4px 8px);position:absolute;inset:0}.column-block__header{flex-wrap:wrap;align-items:center;gap:4px 6px;margin-bottom:8px;display:flex}.column-block__icon{color:#6c757d;flex:none;font-size:.75em}.column-block__title{color:#6c757d;text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;font-size:.75em;font-weight:600;overflow:hidden}.column-block__title a{color:inherit;cursor:pointer;text-decoration:none}.column-block__title a:hover{text-decoration:underline}.column-block__header-meta{flex-wrap:wrap;flex:0 auto;align-items:center;gap:6px;min-width:0;margin-left:auto;display:flex}@container column-block (width<=240px){.column-block .column-block__header-meta{flex-basis:100%;justify-content:flex-end;margin-left:0}}.column-block__body{flex-direction:column;gap:8px;display:flex}.column-block--collapsed .column-block__body{display:none}.column-block__add-button{color:#6c757d;cursor:pointer;background:0 0;border:1px dashed #6c757d;border-radius:4px;width:100%;padding:8px;font-size:13px;transition:border-color .15s,color .15s}.column-block__add-button:hover{color:#2563eb;border-color:#2563eb}.column-block--drop-target{background:#2563eb1a;outline:2px solid #2563eb}.column-block--drop-target .empty-state{border-color:#2563eb}.drag-handle{cursor:grab;touch-action:none;background:0 0;border:none;flex-shrink:0;justify-content:center;align-items:center;width:24px;height:24px;padding:0;display:inline-flex}.drag-handle:active{cursor:grabbing}.drag-handle__icon{opacity:.5;background-image:radial-gradient(circle,currentColor 1.5px,#0000 1.5px),radial-gradient(circle,currentColor 1.5px,#0000 1.5px);background-position:0 0,5px 0;background-size:5px 5px;width:10px;height:14px;display:block}.drag-handle:hover .drag-handle__icon{opacity:.8}.drag-overlay-content{opacity:.85;pointer-events:none;background:#fff;border-radius:4px;flex-wrap:wrap;align-items:baseline;gap:2px 4px;padding:8px 12px;display:flex;box-shadow:0 4px 12px #00000026}.drag-overlay-content__title{color:#333;font-size:13px;font-weight:600;line-height:1.3}.drag-overlay-content__meta{color:#6c757d;flex-basis:100%;font-size:12px}.drag-overlay-content__icon{color:#6c757d;font-size:12px}.element-card{border-left:3px solid #0000}.element-card--draft{border-left-color:#0071c4}.element-card--published{border-left-color:#3fa142}.element-card--modified{border-left-color:#d4a017}.element-card--removed{border-left-color:#c4001d}.element-card{background:#fff;border-radius:4px;padding:8px 12px;box-shadow:0 1px 3px #00000014}.element-card__header{align-items:baseline;gap:6px;display:flex}.element-card__title{margin:0;font-size:.75em;font-weight:600}.element-card__icon{color:#6c757d;font-size:.75em}.element-card--clickable{cursor:pointer}.element-card--clickable:hover{box-shadow:0 1px 6px #00000026}.element-card[style*=transform]{z-index:1}.element-type-picker{border:1px solid var(--border-color-light,#ccc);border-radius:4px;width:90vw;max-width:600px;padding:0}.element-type-picker::backdrop{background:#0006}.element-type-picker__header{border-bottom:1px solid var(--border-color-light,#eee);justify-content:space-between;align-items:center;padding:12px 16px;display:flex}.element-type-picker__title{margin:0;font-size:16px;font-weight:600}.element-type-picker__close{cursor:pointer;color:var(--text-color-muted,#666);background:0 0;border:none;padding:0 4px;font-size:24px;line-height:1}.element-type-picker__body{padding:16px}.element-type-picker__grid{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;display:grid}.element-type-picker__tile{border:1px solid var(--border-color-light,#ddd);background:var(--bg-color-light,#fff);cursor:pointer;text-align:center;border-radius:4px;flex-direction:column;align-items:center;gap:6px;padding:16px 12px;transition:border-color .15s,box-shadow .15s;display:flex}.element-type-picker__tile:hover{border-color:var(--color-primary,#0071c4);box-shadow:0 0 0 1px var(--color-primary,#0071c4)}.element-type-picker__icon{color:var(--text-color-muted,#666);font-size:24px}.element-type-picker__label{font-size:13px;font-weight:600}.element-type-picker__description{color:var(--text-color-muted,#888);font-size:11px;line-height:1.3}.element-type-picker__empty{text-align:center;color:var(--text-color-muted,#888);margin:0;padding:24px 0}.empty-state{color:#6c757d;text-align:center;border:1px dashed #dee2e6;padding:16px}.empty-state--centered{justify-content:center;align-items:center;min-height:120px;display:flex}.row-block{background:#fff;border:1px solid #dee2e6;border-left:3px solid #0000;border-radius:4px;padding:12px}.row-block--draft{border-left-color:#0071c4}.row-block--published{border-left-color:#3fa142}.row-block--modified{border-left-color:#d4a017}.row-block--removed{border-left-color:#c4001d}.row-block__header{align-items:center;gap:6px;margin-bottom:8px;display:flex}.row-block__icon{color:#6c757d;font-size:.75em}.row-block__title{color:#6c757d;margin:0;font-size:.75em;font-weight:600}.row-block__title a{color:inherit;cursor:pointer;text-decoration:none}.row-block__title a:hover{text-decoration:underline}.row-block--collapsed>:not(.row-block__header){display:none}.row-block--drop-target{background:#2563eb1a;border-color:#2563eb}.row-block--drop-target .empty-state{border-color:#2563eb}.row-block[style*=transform]{z-index:1}.row-block__columns--flex{flex-wrap:wrap;row-gap:8px;display:flex}.row-block__columns--grid{grid-template-columns:repeat(var(--grid-columns,12), 1fr);gap:8px;display:grid}.row-block__column{box-sizing:border-box}.row-block__columns--flex>.row-block__column{flex:0 0 var(--col-width);max-width:var(--col-width);margin-left:var(--col-offset,0);padding:0 4px}.row-block__columns--grid>.row-block__column{grid-column:var(--col-start,auto)/span var(--col-span)}.row-block__columns--flex>.add-child-button{flex:0 0 100%;padding:0 4px}.row-block__columns--grid>.add-child-button{grid-column:1/-1}.section-block{background:#f0f4f8;border:2px solid #c0c8d0;border-left:3px solid #0000;border-radius:4px;width:100%;padding:16px}.section-block--draft{border-left-color:#0071c4}.section-block--published{border-left-color:#3fa142}.section-block--modified{border-left-color:#d4a017}.section-block--removed{border-left-color:#c4001d}.section-block__header{align-items:center;gap:6px;margin-bottom:12px;display:flex}.section-block__icon{color:#6c757d;font-size:.75em}.section-block__title{color:#3a4a5c;margin:0;font-size:.75em;font-weight:600}.section-block__title a{color:inherit;cursor:pointer;text-decoration:none}.section-block__title a:hover{text-decoration:underline}.section-block__body{flex-direction:column;gap:12px;display:flex}.section-block--collapsed .section-block__body{display:none}.section-block--drop-target{background:#2563eb1a;border-color:#2563eb}.section-block--drop-target .empty-state{border-color:#2563eb}.section-block[style*=transform]{z-index:1}.viewport-switcher{align-items:center;gap:12px;display:flex}.viewport-switcher [role=group]{border:1px solid #ccc;border-radius:4px;display:flex;overflow:hidden}.viewport-switcher__button{color:#333;cursor:pointer;background:#fff;border:none;padding:6px 16px;font-size:13px;line-height:1.4;transition:background-color .15s,color .15s}.viewport-switcher__button+.viewport-switcher__button{border-left:1px solid #ccc}.viewport-switcher__button:hover:not(.viewport-switcher__button--active){background-color:#f0f0f0}.viewport-switcher__button--active{color:#fff;background-color:#0071c4}.viewport-switcher__reset{color:#c44;cursor:pointer;background:0 0;border:1px solid #c44;border-radius:4px;padding:4px 12px;font-size:12px;line-height:1.4;transition:background-color .15s,color .15s}.viewport-switcher__reset:hover{color:#fff;background-color:#c44}.grid-editor__header{align-items:center;gap:12px;margin-bottom:12px;display:flex}.inspect-toggle{color:#333;cursor:pointer;background:#fff;border:1px solid #ccc;border-radius:4px;padding:6px 16px;font-size:13px;line-height:1.4;transition:background-color .15s,color .15s}.inspect-toggle:hover:not(.inspect-toggle--active,:disabled){background-color:#f0f0f0}.inspect-toggle:disabled{opacity:.5;cursor:not-allowed}.inspect-toggle--active{color:#fff;background-color:#0071c4;border-color:#0071c4}.inspect-halo{border:2px solid #0071c4;border-radius:4px;transition:border-color .15s,box-shadow .15s;box-shadow:0 0 0 2px #0071c440}.inspect-halo--indirect{box-shadow:none;border-style:dashed;border-color:#7ba9d8}.inspect-halo--missing{box-shadow:none;border-style:dashed;border-color:#9a9a9a}.inspect-breadcrumb{color:#f8fafc;pointer-events:none;letter-spacing:.01em;white-space:nowrap;background:#1f2937;border-radius:6px;align-items:center;gap:6px;max-width:80vw;padding:6px 12px;font-size:12px;line-height:1.4;display:inline-flex;box-shadow:0 4px 12px #0f172a4d}.inspect-breadcrumb__segment{text-overflow:ellipsis;align-items:center;max-width:160px;display:inline-flex;overflow:hidden}.inspect-breadcrumb__segment--hidden{color:#f8fafc73;font-style:italic}.inspect-breadcrumb__segment--hidden:after{content:" ↴";opacity:.7;margin-left:2px;font-size:10px;font-style:normal}.inspect-breadcrumb__segment--target{color:#7dd3fc;font-weight:600}.inspect-breadcrumb__separator{color:#f8fafc59;-webkit-user-select:none;user-select:none;font-size:11px}.inspect-breadcrumb--missing{color:#f8fafc73;background:#374151}.grid-editor-holder>.grid-editor__container{flex:0 0 100%;max-width:100%;margin-left:0}.grid-editor{position:relative} /*$vite$:1*/ \ No newline at end of file diff --git a/client/dist/styles/preview.css b/client/dist/styles/preview.css new file mode 100644 index 00000000..006d7c42 --- /dev/null +++ b/client/dist/styles/preview.css @@ -0,0 +1,2 @@ +.grid-inspect-target{background:#2196f31f!important;outline:3px solid #1976d2!important}.grid-inspect-ancestor-1{outline-offset:2px;outline:2px solid #2196f3bf!important}.grid-inspect-ancestor-2{outline-offset:4px;outline:2px solid #2196f380!important}.grid-inspect-ancestor-3{outline-offset:6px;outline:2px solid #2196f340!important}.grid-inspect-breadcrumb{color:#fff;white-space:nowrap;pointer-events:none;z-index:2147483647;background:#212121;border-radius:4px;padding:4px 10px;font:11px -apple-system,system-ui,sans-serif;position:fixed;box-shadow:0 2px 8px #0003} +/*$vite$:1*/ \ No newline at end of file diff --git a/client/lang/en.js b/client/lang/en.js index ac0313a1..ec0a3745 100644 --- a/client/lang/en.js +++ b/client/lang/en.js @@ -1,4 +1,4 @@ // Generated by scripts/collect-js-i18n.mjs — do not edit by hand if (typeof window !== "undefined" && window.ss && window.ss.i18n) { - window.ss.i18n.addDictionary("en", {"WeDevelopGrid.ActionsMenu.TRIGGER_LABEL":"Actions","WeDevelopGrid.AddChildButton.ADD_LABEL":"Add {childLabel}","WeDevelopGrid.AddChildButton.ADDING_LABEL":"Adding {childLabel}…","WeDevelopGrid.AddChildButton.EMPTY_MESSAGE":"No {childLabel}s yet","WeDevelopGrid.CollapseToggle.COLLAPSE_LABEL":"Collapse {label}","WeDevelopGrid.CollapseToggle.EXPAND_LABEL":"Expand {label}","WeDevelopGrid.ColumnBlock.ADD_CONTENT_BUTTON":"+ Add content","WeDevelopGrid.ColumnBlock.MOVE_LABEL":"Move {title}","WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS":"No content blocks","WeDevelopGrid.ConfirmDialog.CANCEL_BUTTON":"Cancel","WeDevelopGrid.DragHandle.DEFAULT_LABEL":"Drag to reorder","WeDevelopGrid.DragOverlayContent.COLUMN_SINGULAR":"column","WeDevelopGrid.DragOverlayContent.ROW_SINGULAR":"row","WeDevelopGrid.DuplicateToDialog.BACK_BUTTON":"Back","WeDevelopGrid.DuplicateToDialog.CANCEL_BUTTON":"Cancel","WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON":"Confirm","WeDevelopGrid.DuplicateToDialog.CONFIRM_SUMMARY_PREFIX":"Duplicate section to zone","WeDevelopGrid.DuplicateToDialog.LOADING_CONTAINERS":"Loading containers…","WeDevelopGrid.DuplicateToDialog.LOADING_PAGES":"Loading pages…","WeDevelopGrid.DuplicateToDialog.LOADING_ZONES":"Loading zones…","WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON":"Next","WeDevelopGrid.DuplicateToDialog.NO_CONTAINERS":"No compatible containers found in this zone","WeDevelopGrid.DuplicateToDialog.SEARCH_PLACEHOLDER":"Search pages…","WeDevelopGrid.DuplicateToDialog.STEP_CONFIRM_TITLE":"Confirm duplication","WeDevelopGrid.DuplicateToDialog.STEP_CONTAINER_TITLE":"Select container","WeDevelopGrid.DuplicateToDialog.STEP_PAGE_TITLE":"Select target page","WeDevelopGrid.DuplicateToDialog.STEP_ZONE_TITLE":"Select zone","WeDevelopGrid.ElementActions.ARCHIVE_CONFIRM_LABEL":"Archive","WeDevelopGrid.ElementCard.MOVE_LABEL":"Move {title}","WeDevelopGrid.ElementTypePicker.CLOSE_LABEL":"Close","WeDevelopGrid.ElementTypePicker.EMPTY_MESSAGE":"No content element types available","WeDevelopGrid.ElementTypePicker.TITLE":"Add content element","WeDevelopGrid.GridEditor.LOAD_ERROR":"Failed to load elements: {message}","WeDevelopGrid.GridEditor.LOADING":"Loading elements...","WeDevelopGrid.GridEditor.NO_SECTIONS":"No sections yet","WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY":"No sections in this version","WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK":"The grid editor failed to render. Try reloading the page.","WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR":"The grid editor encountered an error and could not render.","WeDevelopGrid.RowBlock.MOVE_LABEL":"Move {title}","WeDevelopGrid.SectionBlock.MOVE_LABEL":"Move {title}","WeDevelopGrid.useArchiveAction.ACTION_LABEL":"Archive","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_MANY_CHILDREN":"Archive \"{title}\" and all {count} child elements?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_ONE_CHILD":"Archive \"{title}\" and all {count} child element?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_SIMPLE":"Archive \"{title}\"?","WeDevelopGrid.useArchiveAction.DIALOG_TITLE":"Confirm archive","WeDevelopGrid.useDuplicateAction.ACTION_LABEL":"Duplicate","WeDevelopGrid.useDuplicateToAction.ACTION_LABEL":"Duplicate to…","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_MANY":"Reset all viewport overrides across {count} columns?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_ONE":"Reset all viewport overrides across {count} column?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_MANY":"Reset overrides for {count} columns on {viewport}?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_ONE":"Reset overrides for {count} column on {viewport}?","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_ALL":"Reset all overrides","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_VIEWPORT":"Reset {viewport} overrides","WeDevelopGrid.useResetOverridesAction.RESET_ALL_LABEL":"Reset all","WeDevelopGrid.useResetOverridesAction.RESET_VIEWPORT_LABEL":"Reset viewport","WeDevelopGrid.ViewportSwitcher.GROUP_LABEL":"Viewport size","WeDevelopGrid.ViewportSwitcher.RESET_CONFIRM_LABEL":"Reset"}); + window.ss.i18n.addDictionary("en", {"WeDevelopGrid.ActionsMenu.TRIGGER_LABEL":"Actions","WeDevelopGrid.AddChildButton.ADD_LABEL":"Add {childLabel}","WeDevelopGrid.AddChildButton.ADDING_LABEL":"Adding {childLabel}…","WeDevelopGrid.AddChildButton.EMPTY_MESSAGE":"No {childLabel}s yet","WeDevelopGrid.CollapseToggle.COLLAPSE_LABEL":"Collapse {label}","WeDevelopGrid.CollapseToggle.EXPAND_LABEL":"Expand {label}","WeDevelopGrid.ColumnBlock.ADD_CONTENT_BUTTON":"+ Add content","WeDevelopGrid.ColumnBlock.MOVE_LABEL":"Move {title}","WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS":"No content blocks","WeDevelopGrid.ConfirmDialog.CANCEL_BUTTON":"Cancel","WeDevelopGrid.DragHandle.DEFAULT_LABEL":"Drag to reorder","WeDevelopGrid.DragOverlayContent.COLUMN_SINGULAR":"column","WeDevelopGrid.DragOverlayContent.ROW_SINGULAR":"row","WeDevelopGrid.DuplicateToDialog.BACK_BUTTON":"Back","WeDevelopGrid.DuplicateToDialog.CANCEL_BUTTON":"Cancel","WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON":"Confirm","WeDevelopGrid.DuplicateToDialog.CONFIRM_SUMMARY_PREFIX":"Duplicate section to zone","WeDevelopGrid.DuplicateToDialog.LOADING_CONTAINERS":"Loading containers…","WeDevelopGrid.DuplicateToDialog.LOADING_PAGES":"Loading pages…","WeDevelopGrid.DuplicateToDialog.LOADING_ZONES":"Loading zones…","WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON":"Next","WeDevelopGrid.DuplicateToDialog.NO_CONTAINERS":"No compatible containers found in this zone","WeDevelopGrid.DuplicateToDialog.SEARCH_PLACEHOLDER":"Search pages…","WeDevelopGrid.DuplicateToDialog.STEP_CONFIRM_TITLE":"Confirm duplication","WeDevelopGrid.DuplicateToDialog.STEP_CONTAINER_TITLE":"Select container","WeDevelopGrid.DuplicateToDialog.STEP_PAGE_TITLE":"Select target page","WeDevelopGrid.DuplicateToDialog.STEP_ZONE_TITLE":"Select zone","WeDevelopGrid.ElementActions.ARCHIVE_CONFIRM_LABEL":"Archive","WeDevelopGrid.ElementCard.MOVE_LABEL":"Move {title}","WeDevelopGrid.ElementTypePicker.CLOSE_LABEL":"Close","WeDevelopGrid.ElementTypePicker.EMPTY_MESSAGE":"No content element types available","WeDevelopGrid.ElementTypePicker.TITLE":"Add content element","WeDevelopGrid.GridEditor.LOAD_ERROR":"Failed to load elements: {message}","WeDevelopGrid.GridEditor.LOADING":"Loading elements...","WeDevelopGrid.GridEditor.NO_SECTIONS":"No sections yet","WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY":"No sections in this version","WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK":"The grid editor failed to render. Try reloading the page.","WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR":"The grid editor encountered an error and could not render.","WeDevelopGrid.InspectToggle.DISABLED_TITLE":"Open split-mode preview to use inspect","WeDevelopGrid.InspectToggle.LABEL":"Inspect","WeDevelopGrid.RowBlock.MOVE_LABEL":"Move {title}","WeDevelopGrid.SectionBlock.MOVE_LABEL":"Move {title}","WeDevelopGrid.useArchiveAction.ACTION_LABEL":"Archive","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_MANY_CHILDREN":"Archive \"{title}\" and all {count} child elements?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_ONE_CHILD":"Archive \"{title}\" and all {count} child element?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_SIMPLE":"Archive \"{title}\"?","WeDevelopGrid.useArchiveAction.DIALOG_TITLE":"Confirm archive","WeDevelopGrid.useDuplicateAction.ACTION_LABEL":"Duplicate","WeDevelopGrid.useDuplicateToAction.ACTION_LABEL":"Duplicate to…","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_MANY":"Reset all viewport overrides across {count} columns?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_ONE":"Reset all viewport overrides across {count} column?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_MANY":"Reset overrides for {count} columns on {viewport}?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_ONE":"Reset overrides for {count} column on {viewport}?","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_ALL":"Reset all overrides","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_VIEWPORT":"Reset {viewport} overrides","WeDevelopGrid.useResetOverridesAction.RESET_ALL_LABEL":"Reset all","WeDevelopGrid.useResetOverridesAction.RESET_VIEWPORT_LABEL":"Reset viewport","WeDevelopGrid.ViewportSwitcher.GROUP_LABEL":"Viewport size","WeDevelopGrid.ViewportSwitcher.RESET_CONFIRM_LABEL":"Reset"}); } diff --git a/client/lang/nl.js b/client/lang/nl.js index d54b9ca0..0951bee4 100644 --- a/client/lang/nl.js +++ b/client/lang/nl.js @@ -1,4 +1,4 @@ // Generated by scripts/collect-js-i18n.mjs — do not edit by hand if (typeof window !== "undefined" && window.ss && window.ss.i18n) { - window.ss.i18n.addDictionary("nl", {"WeDevelopGrid.ActionsMenu.TRIGGER_LABEL":"Acties","WeDevelopGrid.AddChildButton.ADD_LABEL":"{childLabel} toevoegen","WeDevelopGrid.AddChildButton.ADDING_LABEL":"Bezig met toevoegen van {childLabel}…","WeDevelopGrid.AddChildButton.EMPTY_MESSAGE":"Nog geen {childLabel}s","WeDevelopGrid.CollapseToggle.COLLAPSE_LABEL":"{label} inklappen","WeDevelopGrid.CollapseToggle.EXPAND_LABEL":"{label} uitklappen","WeDevelopGrid.ColumnBlock.ADD_CONTENT_BUTTON":"+ Inhoud toevoegen","WeDevelopGrid.ColumnBlock.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS":"Geen inhoudsblokken","WeDevelopGrid.ConfirmDialog.CANCEL_BUTTON":"Annuleren","WeDevelopGrid.DragHandle.DEFAULT_LABEL":"Sleep om te herordenen","WeDevelopGrid.DragOverlayContent.COLUMN_SINGULAR":"kolom","WeDevelopGrid.DragOverlayContent.ROW_SINGULAR":"rij","WeDevelopGrid.DuplicateToDialog.BACK_BUTTON":"Terug","WeDevelopGrid.DuplicateToDialog.CANCEL_BUTTON":"Annuleren","WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON":"Bevestigen","WeDevelopGrid.DuplicateToDialog.CONFIRM_SUMMARY_PREFIX":"Sectie dupliceren naar zone","WeDevelopGrid.DuplicateToDialog.LOADING_CONTAINERS":"Containers laden…","WeDevelopGrid.DuplicateToDialog.LOADING_PAGES":"Paginas laden…","WeDevelopGrid.DuplicateToDialog.LOADING_ZONES":"Zones laden…","WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON":"Volgende","WeDevelopGrid.DuplicateToDialog.NO_CONTAINERS":"Geen compatibele containers gevonden in deze zone","WeDevelopGrid.DuplicateToDialog.SEARCH_PLACEHOLDER":"Paginas zoeken…","WeDevelopGrid.DuplicateToDialog.STEP_CONFIRM_TITLE":"Duplicatie bevestigen","WeDevelopGrid.DuplicateToDialog.STEP_CONTAINER_TITLE":"Container selecteren","WeDevelopGrid.DuplicateToDialog.STEP_PAGE_TITLE":"Doelpagina selecteren","WeDevelopGrid.DuplicateToDialog.STEP_ZONE_TITLE":"Zone selecteren","WeDevelopGrid.ElementActions.ARCHIVE_CONFIRM_LABEL":"Archiveren","WeDevelopGrid.ElementCard.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.ElementTypePicker.CLOSE_LABEL":"Sluiten","WeDevelopGrid.ElementTypePicker.EMPTY_MESSAGE":"Geen inhoudselement-types beschikbaar","WeDevelopGrid.ElementTypePicker.TITLE":"Inhoudselement toevoegen","WeDevelopGrid.GridEditor.LOAD_ERROR":"Kon elementen niet laden: {message}","WeDevelopGrid.GridEditor.LOADING":"Elementen laden...","WeDevelopGrid.GridEditor.NO_SECTIONS":"Nog geen secties","WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY":"Geen secties in deze versie","WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK":"De grid-editor kon niet worden weergegeven. Probeer de pagina opnieuw te laden.","WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR":"De grid-editor is tegen een fout aangelopen en kon niet worden weergegeven.","WeDevelopGrid.RowBlock.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.SectionBlock.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.useArchiveAction.ACTION_LABEL":"Archiveren","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_MANY_CHILDREN":"\"{title}\" en alle {count} onderliggende elementen archiveren?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_ONE_CHILD":"\"{title}\" en het {count} onderliggende element archiveren?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_SIMPLE":"\"{title}\" archiveren?","WeDevelopGrid.useArchiveAction.DIALOG_TITLE":"Archiveren bevestigen","WeDevelopGrid.useDuplicateAction.ACTION_LABEL":"Dupliceren","WeDevelopGrid.useDuplicateToAction.ACTION_LABEL":"Dupliceren naar…","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_MANY":"Alle viewport-overrides over {count} kolommen resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_ONE":"Alle viewport-overrides over {count} kolom resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_MANY":"Overrides voor {count} kolommen op {viewport} resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_ONE":"Overrides voor {count} kolom op {viewport} resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_ALL":"Alle overrides resetten","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_VIEWPORT":"{viewport}-overrides resetten","WeDevelopGrid.useResetOverridesAction.RESET_ALL_LABEL":"Alle resetten","WeDevelopGrid.useResetOverridesAction.RESET_VIEWPORT_LABEL":"Viewport resetten","WeDevelopGrid.ViewportSwitcher.GROUP_LABEL":"Viewport-formaat","WeDevelopGrid.ViewportSwitcher.RESET_CONFIRM_LABEL":"Resetten"}); + window.ss.i18n.addDictionary("nl", {"WeDevelopGrid.ActionsMenu.TRIGGER_LABEL":"Acties","WeDevelopGrid.AddChildButton.ADD_LABEL":"{childLabel} toevoegen","WeDevelopGrid.AddChildButton.ADDING_LABEL":"Bezig met toevoegen van {childLabel}…","WeDevelopGrid.AddChildButton.EMPTY_MESSAGE":"Nog geen {childLabel}s","WeDevelopGrid.CollapseToggle.COLLAPSE_LABEL":"{label} inklappen","WeDevelopGrid.CollapseToggle.EXPAND_LABEL":"{label} uitklappen","WeDevelopGrid.ColumnBlock.ADD_CONTENT_BUTTON":"+ Inhoud toevoegen","WeDevelopGrid.ColumnBlock.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.ColumnBlock.NO_CONTENT_BLOCKS":"Geen inhoudsblokken","WeDevelopGrid.ConfirmDialog.CANCEL_BUTTON":"Annuleren","WeDevelopGrid.DragHandle.DEFAULT_LABEL":"Sleep om te herordenen","WeDevelopGrid.DragOverlayContent.COLUMN_SINGULAR":"kolom","WeDevelopGrid.DragOverlayContent.ROW_SINGULAR":"rij","WeDevelopGrid.DuplicateToDialog.BACK_BUTTON":"Terug","WeDevelopGrid.DuplicateToDialog.CANCEL_BUTTON":"Annuleren","WeDevelopGrid.DuplicateToDialog.CONFIRM_BUTTON":"Bevestigen","WeDevelopGrid.DuplicateToDialog.CONFIRM_SUMMARY_PREFIX":"Sectie dupliceren naar zone","WeDevelopGrid.DuplicateToDialog.LOADING_CONTAINERS":"Containers laden…","WeDevelopGrid.DuplicateToDialog.LOADING_PAGES":"Paginas laden…","WeDevelopGrid.DuplicateToDialog.LOADING_ZONES":"Zones laden…","WeDevelopGrid.DuplicateToDialog.NEXT_BUTTON":"Volgende","WeDevelopGrid.DuplicateToDialog.NO_CONTAINERS":"Geen compatibele containers gevonden in deze zone","WeDevelopGrid.DuplicateToDialog.SEARCH_PLACEHOLDER":"Paginas zoeken…","WeDevelopGrid.DuplicateToDialog.STEP_CONFIRM_TITLE":"Duplicatie bevestigen","WeDevelopGrid.DuplicateToDialog.STEP_CONTAINER_TITLE":"Container selecteren","WeDevelopGrid.DuplicateToDialog.STEP_PAGE_TITLE":"Doelpagina selecteren","WeDevelopGrid.DuplicateToDialog.STEP_ZONE_TITLE":"Zone selecteren","WeDevelopGrid.ElementActions.ARCHIVE_CONFIRM_LABEL":"Archiveren","WeDevelopGrid.ElementCard.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.ElementTypePicker.CLOSE_LABEL":"Sluiten","WeDevelopGrid.ElementTypePicker.EMPTY_MESSAGE":"Geen inhoudselement-types beschikbaar","WeDevelopGrid.ElementTypePicker.TITLE":"Inhoudselement toevoegen","WeDevelopGrid.GridEditor.LOAD_ERROR":"Kon elementen niet laden: {message}","WeDevelopGrid.GridEditor.LOADING":"Elementen laden...","WeDevelopGrid.GridEditor.NO_SECTIONS":"Nog geen secties","WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY":"Geen secties in deze versie","WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK":"De grid-editor kon niet worden weergegeven. Probeer de pagina opnieuw te laden.","WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR":"De grid-editor is tegen een fout aangelopen en kon niet worden weergegeven.","WeDevelopGrid.InspectToggle.DISABLED_TITLE":"Open split-modus preview om inspect te gebruiken","WeDevelopGrid.InspectToggle.LABEL":"Inspecteren","WeDevelopGrid.RowBlock.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.SectionBlock.MOVE_LABEL":"{title} verplaatsen","WeDevelopGrid.useArchiveAction.ACTION_LABEL":"Archiveren","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_MANY_CHILDREN":"\"{title}\" en alle {count} onderliggende elementen archiveren?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_ONE_CHILD":"\"{title}\" en het {count} onderliggende element archiveren?","WeDevelopGrid.useArchiveAction.CONFIRM_MESSAGE_SIMPLE":"\"{title}\" archiveren?","WeDevelopGrid.useArchiveAction.DIALOG_TITLE":"Archiveren bevestigen","WeDevelopGrid.useDuplicateAction.ACTION_LABEL":"Dupliceren","WeDevelopGrid.useDuplicateToAction.ACTION_LABEL":"Dupliceren naar…","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_MANY":"Alle viewport-overrides over {count} kolommen resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_ALL_ONE":"Alle viewport-overrides over {count} kolom resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_MANY":"Overrides voor {count} kolommen op {viewport} resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_MESSAGE_VIEWPORT_ONE":"Overrides voor {count} kolom op {viewport} resetten?","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_ALL":"Alle overrides resetten","WeDevelopGrid.useResetOverridesAction.DIALOG_TITLE_VIEWPORT":"{viewport}-overrides resetten","WeDevelopGrid.useResetOverridesAction.RESET_ALL_LABEL":"Alle resetten","WeDevelopGrid.useResetOverridesAction.RESET_VIEWPORT_LABEL":"Viewport resetten","WeDevelopGrid.ViewportSwitcher.GROUP_LABEL":"Viewport-formaat","WeDevelopGrid.ViewportSwitcher.RESET_CONFIRM_LABEL":"Resetten"}); } diff --git a/client/lang/src/en.json b/client/lang/src/en.json index 92246bdd..2ef5b08f 100644 --- a/client/lang/src/en.json +++ b/client/lang/src/en.json @@ -37,6 +37,8 @@ "WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY": "No sections in this version", "WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK": "The grid editor failed to render. Try reloading the page.", "WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR": "The grid editor encountered an error and could not render.", + "WeDevelopGrid.InspectToggle.DISABLED_TITLE": "Open split-mode preview to use inspect", + "WeDevelopGrid.InspectToggle.LABEL": "Inspect", "WeDevelopGrid.RowBlock.MOVE_LABEL": "Move {title}", "WeDevelopGrid.SectionBlock.MOVE_LABEL": "Move {title}", "WeDevelopGrid.useArchiveAction.ACTION_LABEL": "Archive", diff --git a/client/lang/src/nl.json b/client/lang/src/nl.json index 50e77068..8b492ad7 100644 --- a/client/lang/src/nl.json +++ b/client/lang/src/nl.json @@ -37,6 +37,8 @@ "WeDevelopGrid.GridEditor.NO_SECTIONS_READONLY": "Geen secties in deze versie", "WeDevelopGrid.GridEditorErrorBoundary.RENDER_FALLBACK": "De grid-editor kon niet worden weergegeven. Probeer de pagina opnieuw te laden.", "WeDevelopGrid.GridEditorErrorBoundary.TOAST_ERROR": "De grid-editor is tegen een fout aangelopen en kon niet worden weergegeven.", + "WeDevelopGrid.InspectToggle.DISABLED_TITLE": "Open split-modus preview om inspect te gebruiken", + "WeDevelopGrid.InspectToggle.LABEL": "Inspecteren", "WeDevelopGrid.RowBlock.MOVE_LABEL": "{title} verplaatsen", "WeDevelopGrid.SectionBlock.MOVE_LABEL": "{title} verplaatsen", "WeDevelopGrid.useArchiveAction.ACTION_LABEL": "Archiveren", diff --git a/client/src/components/ColumnBlock/ColumnBlock.test.tsx b/client/src/components/ColumnBlock/ColumnBlock.test.tsx index 2b31a5f8..201af29b 100644 --- a/client/src/components/ColumnBlock/ColumnBlock.test.tsx +++ b/client/src/components/ColumnBlock/ColumnBlock.test.tsx @@ -60,6 +60,21 @@ afterEach(() => { }); describe('ColumnBlock', () => { + it('renders data-node-id on the outer wrapper for inspect-mode targeting', () => { + mockFetchSuccess({}); + + const column = createColumnNode({ id: 88, childCount: 0, allowedTypes: null }); + + renderWithProviders(); + + // The hover binding lives on the outer `.row-block__column` wrapper + // because that's the actual root of the component. The inner + // `column-block` testid is a direct child. + const inner = screen.getByTestId('column-block'); + const outer = inner.parentElement; + expect(outer).toHaveAttribute('data-node-id', '88'); + }); + it('renders column children (element cards)', () => { mockFetchSuccess({}); diff --git a/client/src/components/ColumnBlock/ColumnBlock.tsx b/client/src/components/ColumnBlock/ColumnBlock.tsx index 353083a8..09c9b113 100644 --- a/client/src/components/ColumnBlock/ColumnBlock.tsx +++ b/client/src/components/ColumnBlock/ColumnBlock.tsx @@ -9,6 +9,7 @@ import { useGridEditorContext } from '@/hooks/GridEditorContext'; import { useReadonly } from '@/hooks/ReadonlyContext'; import { useViewportContext } from '@/hooks/ViewportContext'; import { useCollapse } from '@/hooks/useCollapseState'; +import { useInspectHoverBinding } from '@/inspect/useInspectHoverBinding'; import { useUpdateGridSettings, useCreateContentElement } from '@/hooks/useElementMutations'; import { buildSortableStyle } from '@/utils/sortableStyles'; import { buildBlockClasses } from '@/utils/blockClasses'; @@ -93,6 +94,7 @@ function EditableColumnBlock({ column }: ColumnBlockProps) { const status = getElementStatus(column.statusFlags); const { isCollapsed, onToggle } = useColumnCollapse(column); const { activeType } = useDragContext(); + const hoverBinding = useInspectHoverBinding(column); const updateGridSettings = useUpdateGridSettings(pageId, zone); const createContentElement = useCreateContentElement(pageId, zone); const [isPickerOpen, setPickerOpen] = useState(false); @@ -182,7 +184,7 @@ function EditableColumnBlock({ column }: ColumnBlockProps) { const hasAllowedTypes = Object.keys(allowedTypes).length > 0; return ( -
+
+
diff --git a/client/src/components/ElementCard/ElementCard.test.tsx b/client/src/components/ElementCard/ElementCard.test.tsx index e906bdbe..edbea99f 100644 --- a/client/src/components/ElementCard/ElementCard.test.tsx +++ b/client/src/components/ElementCard/ElementCard.test.tsx @@ -38,6 +38,16 @@ describe('ElementCard', () => { expect(screen.getByTestId('element-card-title')).toHaveTextContent('My Content Block'); }); + it('renders data-node-id on the root for inspect-mode targeting', () => { + mockFetchSuccess({}); + + const element = createSimpleElement({ id: 99 }); + + renderWithProviders(); + + expect(screen.getByTestId('element-card')).toHaveAttribute('data-node-id', '99'); + }); + it('status class applied correctly', () => { mockFetchSuccess({}); diff --git a/client/src/components/ElementCard/ElementCard.tsx b/client/src/components/ElementCard/ElementCard.tsx index ec9151af..8045b020 100644 --- a/client/src/components/ElementCard/ElementCard.tsx +++ b/client/src/components/ElementCard/ElementCard.tsx @@ -3,6 +3,7 @@ import { useSortable } from '@dnd-kit/sortable'; import type { SimpleElementNode } from '@/types/elements'; import { getElementStatus } from '@/types/status'; import { useReadonly } from '@/hooks/ReadonlyContext'; +import { useInspectHoverBinding } from '@/inspect/useInspectHoverBinding'; import { buildSortableStyle } from '@/utils/sortableStyles'; import { t } from '@/i18n'; import DragHandle from '@/components/DragHandle/DragHandle'; @@ -31,6 +32,7 @@ function EditableElementCard({ element }: ElementCardProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: element.nodeKey, }); + const hoverBinding = useInspectHoverBinding(element); const status = getElementStatus(element.statusFlags); const editLink = element.editLink; const isClickable = editLink !== null; @@ -88,6 +90,7 @@ function EditableElementCard({ element }: ElementCardProps) { style={style} className={cardClasses} data-testid="element-card" + {...hoverBinding} onClick={handleAnchorClick} > {header} @@ -96,7 +99,13 @@ function EditableElementCard({ element }: ElementCardProps) { } return ( -
+
{header}
); @@ -105,9 +114,10 @@ function EditableElementCard({ element }: ElementCardProps) { function ReadonlyElementCard({ element }: ElementCardProps) { const status = getElementStatus(element.statusFlags); const cardClasses = `element-card element-card--${status}`; + const hoverBinding = useInspectHoverBinding(element); return ( -
+

diff --git a/client/src/components/GridEditor/GridEditor.tsx b/client/src/components/GridEditor/GridEditor.tsx index c72f9c4a..afa92a96 100644 --- a/client/src/components/GridEditor/GridEditor.tsx +++ b/client/src/components/GridEditor/GridEditor.tsx @@ -9,6 +9,11 @@ import { CollapseContext, useCollapseState } from '@/hooks/useCollapseState'; import { ViewportProvider } from '@/hooks/ViewportContext'; import { GridEditorProvider } from '@/hooks/GridEditorContext'; import { ReadonlyProvider } from '@/hooks/ReadonlyContext'; +import { InspectBridgeHost } from '@/inspect/InspectBridgeHost'; +import { InspectProvider } from '@/inspect/InspectContext'; +import { InspectHoverDelegate } from '@/inspect/InspectHoverDelegate'; +import { InspectOverlay } from '@/inspect/InspectOverlay'; +import { InspectToggle } from '@/inspect/InspectToggle'; import { isSectionNode, type TreeApiResponse } from '@/types/elements'; import type { NodeRef } from '@/types/identity'; import ViewportSwitcher from '@/components/ViewportSwitcher/ViewportSwitcher'; @@ -121,45 +126,56 @@ export default function GridEditor({ pageId, zone, readonly = false, version }:

)} {data !== undefined && pageId !== null && ( - - - - - - {readonly ? ( - sectionList - ) : ( - - - - {sectionList} - {hasSections && ( - + + + + +
+ + +
+ + + {readonly ? ( + <> + + {sectionList} + + ) : ( + + + + + {sectionList} + {hasSections && ( + + )} + + + + {dragState !== null && ( + )} -
-
- - {dragState !== null && ( - - )} - -
- )} -
-
-
-
+ + + )} + + + + + )} {data !== undefined && pageId === null && ( { expect(screen.getByTestId('row-title')).toHaveTextContent('Main Row'); }); + it('renders data-node-id on the root for inspect-mode targeting', () => { + mockFetchSuccess({}); + + const row = createRowNode({ id: 42 }); + + renderWithProviders(); + + expect(screen.getByTestId('row-block')).toHaveAttribute('data-node-id', '42'); + }); + it('renders child columns', () => { mockFetchSuccess({}); diff --git a/client/src/components/RowBlock/RowBlock.tsx b/client/src/components/RowBlock/RowBlock.tsx index 64ce7491..48245549 100644 --- a/client/src/components/RowBlock/RowBlock.tsx +++ b/client/src/components/RowBlock/RowBlock.tsx @@ -7,6 +7,7 @@ import { getElementStatus } from '@/types/status'; import { useDragContext } from '@/hooks/useDragAndDrop'; import { useReadonly } from '@/hooks/ReadonlyContext'; import { useCollapse } from '@/hooks/useCollapseState'; +import { useInspectHoverBinding } from '@/inspect/useInspectHoverBinding'; import { buildSortableStyle } from '@/utils/sortableStyles'; import { buildBlockClasses } from '@/utils/blockClasses'; import { getOffsetStrategy, getColumnCount } from '@/utils/gridAdapter'; @@ -48,6 +49,7 @@ function EditableRowBlock({ row }: RowBlockProps) { const status = getElementStatus(row.statusFlags); const { isCollapsed, onToggle } = useRowCollapse(row); const { activeType } = useDragContext(); + const hoverBinding = useInspectHoverBinding(row); const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } = useSortable({ id: row.nodeKey }); @@ -64,7 +66,13 @@ function EditableRowBlock({ row }: RowBlockProps) { const childKeys = useChildColumnKeys(row); return ( -
+
+
diff --git a/client/src/components/SectionBlock/SectionBlock.test.tsx b/client/src/components/SectionBlock/SectionBlock.test.tsx index 4ba0c213..a42293ac 100644 --- a/client/src/components/SectionBlock/SectionBlock.test.tsx +++ b/client/src/components/SectionBlock/SectionBlock.test.tsx @@ -49,6 +49,16 @@ describe('SectionBlock', () => { expect(screen.getByTestId('section-title')).toHaveTextContent('Hero Section'); }); + it('renders data-node-id on the root for inspect-mode targeting', () => { + mockFetchSuccess({}); + + const section = createSectionNode({ id: 77 }); + + renderWithProviders(); + + expect(screen.getByTestId('section-block')).toHaveAttribute('data-node-id', '77'); + }); + it('renders child rows', () => { mockFetchSuccess({}); diff --git a/client/src/components/SectionBlock/SectionBlock.tsx b/client/src/components/SectionBlock/SectionBlock.tsx index e1071841..ed2b8f24 100644 --- a/client/src/components/SectionBlock/SectionBlock.tsx +++ b/client/src/components/SectionBlock/SectionBlock.tsx @@ -7,6 +7,7 @@ import { getElementStatus } from '@/types/status'; import { useDragContext } from '@/hooks/useDragAndDrop'; import { useReadonly } from '@/hooks/ReadonlyContext'; import { useCollapse } from '@/hooks/useCollapseState'; +import { useInspectHoverBinding } from '@/inspect/useInspectHoverBinding'; import { buildSortableStyle } from '@/utils/sortableStyles'; import { buildBlockClasses } from '@/utils/blockClasses'; import { t } from '@/i18n'; @@ -51,6 +52,7 @@ function EditableSectionBlock({ section }: SectionBlockProps) { const status = getElementStatus(section.statusFlags); const { isCollapsed, onToggle } = useSectionCollapse(section); const { activeType } = useDragContext(); + const hoverBinding = useInspectHoverBinding(section); const { attributes, listeners, setNodeRef, transform, transition, isDragging, isOver } = useSortable({ id: section.nodeKey }); @@ -67,7 +69,13 @@ function EditableSectionBlock({ section }: SectionBlockProps) { const childKeys = useChildSortableKeys(section); return ( -
+
+
diff --git a/client/src/inspect/InspectBridgeHost.test.tsx b/client/src/inspect/InspectBridgeHost.test.tsx new file mode 100644 index 00000000..6d6395be --- /dev/null +++ b/client/src/inspect/InspectBridgeHost.test.tsx @@ -0,0 +1,460 @@ +import { act, render } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { ReactNode } from 'react'; +import { + createColumnNode, + createRowNode, + createSectionNode, + createSimpleElement, + createTreeApiResponse, +} from '@/testing/factories'; +import { installMockLocalStorage } from '@/testing/mockLocalStorage'; +import type { TreeApiResponse } from '@/types/elements'; +import { InspectProvider, useInspect } from './InspectContext'; +import { InspectBridgeHost } from './InspectBridgeHost'; + +// Controllable iframe stub. `resolvePreviewIframe` is mocked so tests don't +// touch real DOM — the returned object records postMessage calls and can +// dispatch its own `load` events. + +const postMessage = vi.fn<(msg: unknown, origin: string) => void>(); +const loadListeners = new Set<(event: Event) => void>(); +const iframeStub: HTMLIFrameElement = { + contentWindow: { postMessage } as unknown as Window, + addEventListener: vi.fn((event: string, fn: (event: Event) => void) => { + if (event === 'load') loadListeners.add(fn); + }), + removeEventListener: vi.fn((event: string, fn: (event: Event) => void) => { + if (event === 'load') loadListeners.delete(fn); + }), +} as unknown as HTMLIFrameElement; + +// Controllable resolver: tests can swap the returned iframe to simulate the +// preview pane mounting after the editor (the real CMS lifecycle). +let currentIframe: HTMLIFrameElement | null = iframeStub; + +vi.mock('./resolvePreviewIframe', () => ({ + resolvePreviewIframe: () => currentIframe, +})); + +// The bridge subscribes via `window.addEventListener('message', ...)`. To +// simulate messages coming from the preview we dispatch same-origin +// MessageEvents; jsdom's window.location.origin is the origin used. + +function dispatchPreviewMessage(data: unknown): void { + window.dispatchEvent(new MessageEvent('message', { origin: window.location.origin, data })); +} + +// Minimal harness that exposes the InspectApi to the test while mounting +// InspectBridgeHost under the same provider so they share state. + +interface HarnessCaptured { + api: ReturnType | null; +} + +function ApiCapture({ captured }: { captured: HarnessCaptured }): null { + captured.api = useInspect(); + return null; +} + +function Harness({ + tree, + captured, + children, +}: { + tree: TreeApiResponse; + captured: HarnessCaptured; + children?: ReactNode; +}): React.JSX.Element { + return ( + + + + {children} + + ); +} + +function buildTree(): TreeApiResponse { + const element = createSimpleElement({ id: 13, parent: { type: 'column', id: 12 } }); + const column = createColumnNode({ + id: 12, + parent: { type: 'row', id: 11 }, + children: [element], + }); + const row = createRowNode({ + id: 11, + parent: { type: 'section', id: 10 }, + children: [column], + }); + const section = createSectionNode({ + id: 10, + parent: { type: 'page', id: 1 }, + children: [row], + }); + return createTreeApiResponse({ rootParent: { type: 'page', id: 1 }, sections: [section] }); +} + +const realLocalStorage = globalThis.localStorage; + +beforeEach(() => { + installMockLocalStorage(); + postMessage.mockClear(); + loadListeners.clear(); + currentIframe = iframeStub; +}); + +afterEach(() => { + Object.defineProperty(globalThis, 'localStorage', { + value: realLocalStorage, + writable: true, + configurable: true, + }); + vi.useRealTimers(); +}); + +describe('InspectBridgeHost', () => { + it('does not send activate on mount when preview is not ready', () => { + const captured: HarnessCaptured = { api: null }; + render(); + act(() => captured.api?.setEnabled(true)); + // Queued — no activate until ready arrives. + expect(postMessage).not.toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); + + it('flushes a queued activate when ready arrives', () => { + const captured: HarnessCaptured = { api: null }; + render(); + act(() => captured.api?.setEnabled(true)); + postMessage.mockClear(); + + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); + + it('sends activate immediately when already ready', () => { + const captured: HarnessCaptured = { api: null }; + render(); + // Preview reports ready before editor toggles on. + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + postMessage.mockClear(); + + act(() => captured.api?.setEnabled(true)); + + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); + + it('sends deactivate on true → false transition', () => { + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + act(() => captured.api?.setEnabled(true)); + postMessage.mockClear(); + + act(() => captured.api?.setEnabled(false)); + + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:deactivate' }, + window.location.origin, + ); + }); + + it('debounces editor hover by 200ms before sending highlight', () => { + vi.useFakeTimers(); + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + act(() => captured.api?.setEnabled(true)); + postMessage.mockClear(); + + const node = createSimpleElement({ id: 13, parent: { type: 'column', id: 12 } }); + act(() => captured.api?.setEditorHover(node)); + + // Not fired yet + expect(postMessage).not.toHaveBeenCalled(); + + // Advance short of 200ms: still not fired + act(() => { + vi.advanceTimersByTime(199); + }); + expect(postMessage).not.toHaveBeenCalled(); + + // Complete the debounce + act(() => { + vi.advanceTimersByTime(1); + }); + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:highlight', id: 13 }, + window.location.origin, + ); + }); + + it('cancels a pending highlight when hover changes before debounce fires', () => { + vi.useFakeTimers(); + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + act(() => captured.api?.setEnabled(true)); + postMessage.mockClear(); + + act(() => + captured.api?.setEditorHover( + createSimpleElement({ id: 13, parent: { type: 'column', id: 12 } }), + ), + ); + act(() => { + vi.advanceTimersByTime(100); + }); + // Change hover before the first timer fires + act(() => + captured.api?.setEditorHover( + createSimpleElement({ id: 99, parent: { type: 'column', id: 12 } }), + ), + ); + act(() => { + vi.advanceTimersByTime(200); + }); + + // Only the latest highlight should have been sent + const highlights = postMessage.mock.calls.filter( + ([msg]) => + typeof msg === 'object' && + msg !== null && + (msg as { type: string }).type === 'grid-inspect:highlight', + ); + expect(highlights).toEqual([ + [{ type: 'grid-inspect:highlight', id: 99 }, window.location.origin], + ]); + }); + + it('does NOT send clear on initial mount with no prior editor hover', () => { + vi.useFakeTimers(); + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + act(() => captured.api?.setEnabled(true)); + + const clearCalls = postMessage.mock.calls.filter( + ([msg]) => + typeof msg === 'object' && + msg !== null && + (msg as { type: string }).type === 'grid-inspect:clear', + ); + expect(clearCalls).toEqual([]); + }); + + it('sends clear when an editor hover transitions back to null', () => { + vi.useFakeTimers(); + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + act(() => captured.api?.setEnabled(true)); + + // First, complete an editor-hover highlight so lastEditorHoverIdRef is set. + const node = createSimpleElement({ id: 13, parent: { type: 'column', id: 12 } }); + act(() => captured.api?.setEditorHover(node)); + act(() => { + vi.advanceTimersByTime(200); + }); + postMessage.mockClear(); + + // Now clear — should dispatch clear immediately. + act(() => captured.api?.setEditorHover(null)); + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:clear' }, + window.location.origin, + ); + }); + + it('invokes setPreviewHover with resolved ancestor ids and full path on preview hover', () => { + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:hover', id: 13 })); + + expect(captured.api?.hover).toMatchObject({ + source: 'preview', + id: 13, + ancestorIds: [10, 11, 12], + }); + // Path walks outermost → target and carries titles + types for the + // editor-side breadcrumb. + const path = captured.api?.hover?.source === 'preview' ? captured.api.hover.path : []; + expect(path.map((p) => p.id)).toEqual([10, 11, 12, 13]); + expect(path.map((p) => p.type)).toEqual(['section', 'row', 'column', 'element']); + }); + + it('invokes clearHover on preview unhover', () => { + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:hover', id: 13 })); + expect(captured.api?.hover).not.toBeNull(); + + act(() => dispatchPreviewMessage({ type: 'grid-inspect:unhover' })); + expect(captured.api?.hover).toBeNull(); + }); + + it('sets missing flag on preview missing message', () => { + const captured: HarnessCaptured = { api: null }; + render(); + act(() => dispatchPreviewMessage({ type: 'grid-inspect:missing', id: 77 })); + expect(captured.api?.missing).toBe(true); + }); + + it('resets ready flag when the iframe emits load', () => { + const captured: HarnessCaptured = { api: null }; + render(); + // Arrive at ready + activated state + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + act(() => captured.api?.setEnabled(true)); + postMessage.mockClear(); + + // Iframe reloads: readyRef resets internally. + act(() => { + for (const listener of loadListeners) listener(new Event('load')); + }); + + // Toggling off/on should now queue (no ready yet) — activate should NOT + // be dispatched until a new ready message arrives. + act(() => captured.api?.setEnabled(false)); + postMessage.mockClear(); + act(() => captured.api?.setEnabled(true)); + expect(postMessage).not.toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + + // New ready → flushes the queued activate. + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); + + it('wires the load listener when the iframe arrives in the DOM after mount', async () => { + // Simulate the production CMS lifecycle: editor mounts before the preview + // iframe is attached. The MutationObserver must pick the iframe up when + // it later appears and wire the `load` listener to it so readyRef is + // reset and queued activates flush on the next preview ready. + currentIframe = null; + + const captured: HarnessCaptured = { api: null }; + render(); + + // Preview not here yet — toggling on queues the activate (no iframe to + // send through). + act(() => captured.api?.setEnabled(true)); + expect(postMessage).not.toHaveBeenCalled(); + + // Late-mount the iframe. Appending a real element to document.body + // triggers the MutationObserver; the wire() callback re-resolves + // resolvePreviewIframe which now returns our stub. + currentIframe = iframeStub; + const marker = document.createElement('div'); + await act(async () => { + document.body.appendChild(marker); + // MutationObserver callbacks are queued as microtasks; flushing the + // microtask queue lets the observer run before we assert. + await Promise.resolve(); + }); + + // Fire a load event on the late-arrived iframe. The listener should + // now be wired, so readyRef resets (no visible side-effect yet — we'll + // prove it through the ready → activate flush). + act(() => { + for (const listener of loadListeners) listener(new Event('load')); + }); + + // The queued activate should still be pending (readyRef is false), so + // the next ready message flushes it. + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + + document.body.removeChild(marker); + }); + + it('renders nothing', () => { + // InspectBridgeHost is a wiring component — it should not inject DOM. + const captured: HarnessCaptured = { api: null }; + const { container } = render( + + sibling + , + ); + // Only the we pass as a child survives. + expect(container.textContent).toBe('sibling'); + }); + + it('sends a deactivate probe when mounting with enabled=true but no ready yet', () => { + // Regression: on reload with inspect previously enabled, the editor + // re-mounts with `enabled=true` directly. If the preview iframe already + // booted and emitted its `ready` before our subscribe attached, that + // ready is lost and `readyRef` stays false forever — leaving the queued + // activate to never flush. The fix: send a `deactivate` probe in the + // queuing branch so the preview echoes `ready` in response, which then + // flips `readyRef` and flushes the queued activate. + const captured: HarnessCaptured = { api: null }; + // Pre-seed localStorage so the provider hydrates with enabled=true. + localStorage.setItem('grid:inspect-mode', 'true'); + render(); + + // The deactivate probe is the only outbound message at this point; the + // activate is queued (readyRef=false) and waiting for ready. + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:deactivate' }, + window.location.origin, + ); + expect(postMessage).not.toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + + // Simulate the preview echoing ready in response to the deactivate probe. + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); + + it('flushes a queued activate when a `ready` arrives as a reply to an outbound control message', () => { + // Regression test for the editor↔preview race: the iframe boots and + // posts its initial `ready` before InspectBridgeHost has wired its + // subscribe effect, so that ready is lost. The fix lives on the preview + // side — it echoes `ready` on every inbound control message. This test + // simulates that echo behaviour and asserts the editor recovers. + const captured: HarnessCaptured = { api: null }; + render(); + // User toggles on before any `ready` has been received. Activate is + // queued but cannot dispatch — readyRef is still false. + act(() => captured.api?.setEnabled(true)); + expect(postMessage).not.toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + + // The preview receives the editor's deactivate (from the initial + // `enabled=false` effect on mount) and echoes `ready` in response. + // With the subscribe now live, that ready unsticks the queue. + act(() => dispatchPreviewMessage({ type: 'grid-inspect:ready' })); + + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); +}); diff --git a/client/src/inspect/InspectBridgeHost.tsx b/client/src/inspect/InspectBridgeHost.tsx new file mode 100644 index 00000000..9fb391ff --- /dev/null +++ b/client/src/inspect/InspectBridgeHost.tsx @@ -0,0 +1,173 @@ +import { useEffect, useRef } from 'react'; +import type { TreeApiResponse } from '@/types/elements'; +import { resolveAncestorIds, resolveAncestorPath } from './ancestry'; +import { useInspect } from './InspectContext'; +import { createMessageBridge } from './messageBridge'; +import { resolvePreviewIframe } from './resolvePreviewIframe'; + +interface Props { + readonly tree: TreeApiResponse; +} + +/** + * Debounce for the editor → preview highlight hop. Mouse hover events fire + * per pixel of movement; without the gate the parent would spam the iframe + * with highlight messages as the cursor crosses a block. + */ +const DEBOUNCE_MS = 200; + +/** + * Invisible wiring component that bridges the InspectContext with the preview + * iframe. Mount it inside an `` and pass the current tree — + * nothing renders; all output is postMessage + context updates. + * + * Responsibilities: + * - Subscribe to inbound preview messages (ready, hover, unhover, missing) + * and translate them into context updates (resolving ancestors for hover). + * - Reset the ready flag when the iframe reloads so a cached activate can + * be replayed once the next ready arrives. + * - Send activate/deactivate in response to the `enabled` flag flipping; + * queue the activate if the preview hasn't yet announced ready. + * - Debounce editor-side hover events to a single highlight message per + * settled target; emit clear only when transitioning OUT of an editor + * hover (never on initial mount or after a preview-source hover). + */ +export function InspectBridgeHost({ tree }: Props): null { + const { enabled, hover, setPreviewHover, clearHover, setMissing } = useInspect(); + // Bridge is a pure value object with no internal state — safe to lazily + // build once per mount. + const bridgeRef = useRef(createMessageBridge(resolvePreviewIframe)); + const readyRef = useRef(false); + const queuedActivateRef = useRef(false); + + // Inbound: translate preview messages into context updates. + useEffect(() => { + const bridge = bridgeRef.current; + const unsubscribe = bridge.subscribe((message) => { + switch (message.type) { + case 'grid-inspect:ready': { + readyRef.current = true; + if (queuedActivateRef.current) { + bridge.send({ type: 'grid-inspect:activate' }); + queuedActivateRef.current = false; + } + return; + } + case 'grid-inspect:hover': { + const ancestors = resolveAncestorIds(tree, message.id); + const path = resolveAncestorPath(tree, message.id); + setPreviewHover(message.id, ancestors, path); + return; + } + case 'grid-inspect:unhover': { + clearHover(); + return; + } + case 'grid-inspect:missing': { + setMissing(true); + return; + } + } + }); + return unsubscribe; + }, [tree, setPreviewHover, clearHover, setMissing]); + + // Reset ready flag on iframe reload — the preview bundle re-announces + // ready on every load, so any stale ready state from before the + // navigation would otherwise cause queued activates to drop silently. + // + // The iframe may not exist in the DOM at mount (CMS navigates lazily into + // page edit → preview pane), and may be swapped out/in across Pjax + // transitions. A single-shot `document.querySelector` at mount would miss + // late arrivals and never re-wire. Follow the same vanilla MutationObserver + // pattern used for entwine Pjax fallback in `client/src/bridge/entwine.ts`: + // observe `document.body` for subtree mutations and re-resolve the iframe + // whenever the DOM shifts, wiring the load listener to whichever iframe + // currently answers `resolvePreviewIframe`. + useEffect(() => { + let listenerIframe: HTMLIFrameElement | null = null; + const onLoad = (): void => { + readyRef.current = false; + }; + + const wire = (iframe: HTMLIFrameElement | null): void => { + if (iframe === listenerIframe) return; + listenerIframe?.removeEventListener('load', onLoad); + listenerIframe = iframe; + listenerIframe?.addEventListener('load', onLoad); + }; + + wire(resolvePreviewIframe()); + + if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') { + return () => { + listenerIframe?.removeEventListener('load', onLoad); + }; + } + + const observer = new MutationObserver(() => { + wire(resolvePreviewIframe()); + }); + observer.observe(document.body, { childList: true, subtree: true }); + + return () => { + observer.disconnect(); + listenerIframe?.removeEventListener('load', onLoad); + }; + }, []); + + // Activate / deactivate handshake. + // + // On mount with `enabled=true` (the common post-reload case when the user + // previously enabled inspect), the preview may have already emitted its + // boot-time `ready` before our subscribe listener attached — so `readyRef` + // stays false and a naive activate would queue forever. Send a probe + // `deactivate` alongside the queued activate: the preview echoes `ready` on + // every inbound message, which flips `readyRef` and flushes the queued + // activate. A no-op deactivate against an already-inactive inspector has no + // side effect, so this is safe even when the timing race didn't actually + // happen. + useEffect(() => { + const bridge = bridgeRef.current; + if (enabled) { + if (readyRef.current) { + bridge.send({ type: 'grid-inspect:activate' }); + return; + } + queuedActivateRef.current = true; + bridge.send({ type: 'grid-inspect:deactivate' }); + return; + } + bridge.send({ type: 'grid-inspect:deactivate' }); + queuedActivateRef.current = false; + }, [enabled]); + + // Editor hover → preview. `lastEditorHoverIdRef` tracks the last HIGHLIGHT + // we actually sent, so `clear` only goes out when transitioning OUT of an + // editor hover (not on initial mount, not after a preview-source hover + // that's being cleared, and not for hovers that never completed their + // debounce). + const lastEditorHoverIdRef = useRef(null); + useEffect(() => { + const bridge = bridgeRef.current; + if (!enabled) { + lastEditorHoverIdRef.current = null; + return; + } + if (hover === null || hover.source !== 'editor') { + if (lastEditorHoverIdRef.current !== null) { + bridge.send({ type: 'grid-inspect:clear' }); + lastEditorHoverIdRef.current = null; + } + return; + } + const id = hover.id; + const timer = window.setTimeout(() => { + bridge.send({ type: 'grid-inspect:highlight', id }); + lastEditorHoverIdRef.current = id; + }, DEBOUNCE_MS); + return () => window.clearTimeout(timer); + }, [hover, enabled]); + + return null; +} diff --git a/client/src/inspect/InspectContext.test.tsx b/client/src/inspect/InspectContext.test.tsx new file mode 100644 index 00000000..cd86b2ac --- /dev/null +++ b/client/src/inspect/InspectContext.test.tsx @@ -0,0 +1,137 @@ +import { act, renderHook } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { ReactNode } from 'react'; +import { createSimpleElement } from '@/testing/factories'; +import { installMockLocalStorage } from '@/testing/mockLocalStorage'; +import { InspectProvider, useInspect } from './InspectContext'; +import { STORAGE_KEY } from './storage'; + +const realLocalStorage = globalThis.localStorage; + +function wrapper({ children }: { children: ReactNode }): React.JSX.Element { + return {children}; +} + +describe('InspectContext', () => { + beforeEach(() => { + installMockLocalStorage(); + }); + afterEach(() => { + Object.defineProperty(globalThis, 'localStorage', { + value: realLocalStorage, + writable: true, + configurable: true, + }); + }); + + it('throws when useInspect is called outside a provider', () => { + // Suppress the React error boundary noise — we only care about the throw. + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(() => renderHook(() => useInspect())).toThrow(/useInspect must be used within/); + errorSpy.mockRestore(); + }); + + it('starts disabled when storage empty', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + expect(result.current.enabled).toBe(false); + expect(result.current.hover).toBeNull(); + expect(result.current.missing).toBe(false); + }); + + it('hydrates enabled from storage', () => { + localStorage.setItem(STORAGE_KEY, 'true'); + const { result } = renderHook(() => useInspect(), { wrapper }); + expect(result.current.enabled).toBe(true); + }); + + it('setEnabled persists and updates state', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + act(() => result.current.setEnabled(true)); + expect(result.current.enabled).toBe(true); + expect(localStorage.getItem(STORAGE_KEY)).toBe('true'); + act(() => result.current.setEnabled(false)); + expect(result.current.enabled).toBe(false); + expect(localStorage.getItem(STORAGE_KEY)).toBe('false'); + }); + + it('syncs across instances via module-scoped broadcast', () => { + const a = renderHook(() => useInspect(), { wrapper }); + const b = renderHook(() => useInspect(), { wrapper }); + act(() => a.result.current.setEnabled(true)); + expect(b.result.current.enabled).toBe(true); + expect(a.result.current.enabled).toBe(true); + }); + + it('responds to a cross-tab storage event', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + localStorage.setItem(STORAGE_KEY, 'true'); + act(() => { + window.dispatchEvent(new StorageEvent('storage', { key: STORAGE_KEY })); + }); + expect(result.current.enabled).toBe(true); + }); + + it('ignores storage events for unrelated keys', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + localStorage.setItem(STORAGE_KEY, 'true'); + act(() => { + window.dispatchEvent(new StorageEvent('storage', { key: 'something-else' })); + }); + expect(result.current.enabled).toBe(false); + }); + + it('setEditorHover sets editor-source hover and clearing resets it', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + const node = createSimpleElement({ id: 5 }); + act(() => result.current.setEditorHover(node)); + expect(result.current.hover).toEqual({ + source: 'editor', + nodeKey: node.nodeKey, + id: 5, + ancestorIds: [], + }); + act(() => result.current.setEditorHover(null)); + expect(result.current.hover).toBeNull(); + }); + + it('setPreviewHover sets preview-source hover with ancestor ids and path', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + const path = [ + { id: 1, title: 'Section 1', type: 'section' as const }, + { id: 2, title: 'Row 1', type: 'row' as const }, + { id: 3, title: 'Column 1', type: 'column' as const }, + { id: 9, title: 'Target', type: 'element' as const }, + ]; + act(() => result.current.setPreviewHover(9, [1, 2, 3], path)); + expect(result.current.hover).toEqual({ + source: 'preview', + id: 9, + ancestorIds: [1, 2, 3], + path, + }); + }); + + it('setMissing toggles missing flag and clearHover resets it', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + act(() => result.current.setMissing(true)); + expect(result.current.missing).toBe(true); + act(() => result.current.clearHover()); + expect(result.current.missing).toBe(false); + expect(result.current.hover).toBeNull(); + }); + + it('setEditorHover clears any missing flag', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + act(() => result.current.setMissing(true)); + const node = createSimpleElement({ id: 5 }); + act(() => result.current.setEditorHover(node)); + expect(result.current.missing).toBe(false); + }); + + it('setPreviewHover clears any missing flag', () => { + const { result } = renderHook(() => useInspect(), { wrapper }); + act(() => result.current.setMissing(true)); + act(() => result.current.setPreviewHover(1, [], [])); + expect(result.current.missing).toBe(false); + }); +}); diff --git a/client/src/inspect/InspectContext.tsx b/client/src/inspect/InspectContext.tsx new file mode 100644 index 00000000..774274c2 --- /dev/null +++ b/client/src/inspect/InspectContext.tsx @@ -0,0 +1,146 @@ +import { + createContext, + type ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; +import type { ElementNode } from '@/types/elements'; +import type { NodeKey } from '@/types/identity'; +import type { PathSegment } from './ancestry'; +import { readEnabled, STORAGE_KEY, writeEnabled } from './storage'; + +/** + * Hover state shared between the editor tree and the preview inspector. + * + * - `source: 'editor'` means the user is hovering a block in the editor + * column; the preview should highlight the matching element. + * - `source: 'preview'` means the user is hovering an element in the preview + * iframe; the editor should highlight the matching block (and ancestors). + */ +export type InspectHover = + | null + | { source: 'editor'; nodeKey: NodeKey; id: number; ancestorIds: number[] } + | { + source: 'preview'; + id: number; + ancestorIds: number[]; + /** + * Outside-in segments from the root section through the hovered target + * (inclusive). Drives the editor-side breadcrumb so the user sees the + * full `Section › Row › Column › Element` trail even when intermediate + * ancestors are collapsed in the tree. + */ + path: readonly PathSegment[]; + }; + +export interface InspectApi { + enabled: boolean; + hover: InspectHover; + missing: boolean; + setEnabled(next: boolean): void; + setEditorHover(node: ElementNode | null): void; + setPreviewHover(id: number, ancestorIds: number[], path: readonly PathSegment[]): void; + clearHover(): void; + setMissing(value: boolean): void; +} + +const Ctx = createContext(null); + +/** + * Module-scoped pub-sub. + * + * Two separate `` instances on the same page (e.g. when the + * editor is mounted alongside another CMS panel) need to stay in sync even + * though React state is local. Writing to storage also fires broadcast so + * every provider on the page reflects the change immediately — the native + * `storage` event only fires on OTHER tabs, never the one that wrote. + */ +const subscribers = new Set<(next: boolean) => void>(); +function broadcast(next: boolean): void { + for (const fn of subscribers) fn(next); +} + +export function InspectProvider({ children }: { children: ReactNode }): React.JSX.Element { + const [enabled, setEnabledState] = useState(() => readEnabled()); + const [hover, setHover] = useState(null); + const [missing, setMissingState] = useState(false); + + useEffect(() => { + const onBroadcast = (next: boolean): void => setEnabledState(next); + subscribers.add(onBroadcast); + + const onStorage = (event: StorageEvent): void => { + if (event.key !== STORAGE_KEY) return; + setEnabledState(readEnabled()); + }; + window.addEventListener('storage', onStorage); + + return () => { + subscribers.delete(onBroadcast); + window.removeEventListener('storage', onStorage); + }; + }, []); + + const setEnabled = useCallback((next: boolean): void => { + writeEnabled(next); + setEnabledState(next); + broadcast(next); + }, []); + + const setEditorHover = useCallback((node: ElementNode | null): void => { + if (node === null) { + setHover(null); + setMissingState(false); + return; + } + // Ancestors resolved by the tree-aware host wrapper (InspectBridgeHost) + // — the context itself stays tree-agnostic so it can be shared by code + // that doesn't have a tree in scope. + setHover({ source: 'editor', nodeKey: node.nodeKey, id: node.id, ancestorIds: [] }); + setMissingState(false); + }, []); + + const setPreviewHover = useCallback( + (id: number, ancestorIds: number[], path: readonly PathSegment[]): void => { + setHover({ source: 'preview', id, ancestorIds, path }); + setMissingState(false); + }, + [], + ); + + const clearHover = useCallback((): void => { + setHover(null); + setMissingState(false); + }, []); + + const setMissing = useCallback((value: boolean): void => { + setMissingState(value); + }, []); + + const value = useMemo( + () => ({ + enabled, + hover, + missing, + setEnabled, + setEditorHover, + setPreviewHover, + clearHover, + setMissing, + }), + [enabled, hover, missing, setEnabled, setEditorHover, setPreviewHover, clearHover, setMissing], + ); + + return {children}; +} + +export function useInspect(): InspectApi { + const ctx = useContext(Ctx); + if (ctx === null) { + throw new Error('useInspect must be used within '); + } + return ctx; +} diff --git a/client/src/inspect/InspectHoverDelegate.test.tsx b/client/src/inspect/InspectHoverDelegate.test.tsx new file mode 100644 index 00000000..aeefc3bc --- /dev/null +++ b/client/src/inspect/InspectHoverDelegate.test.tsx @@ -0,0 +1,237 @@ +import { act, cleanup, render } from '@testing-library/react'; +import type { ReactNode } from 'react'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { DragContext, type DragContextValue } from '@/hooks/useDragAndDrop'; +import { + createColumnNode, + createRowNode, + createSectionNode, + createSimpleElement, + createTreeApiResponse, +} from '@/testing/factories'; +import { installMockLocalStorage } from '@/testing/mockLocalStorage'; +import { InspectProvider, useInspect } from './InspectContext'; +import { InspectHoverDelegate } from './InspectHoverDelegate'; + +const realLocalStorage = globalThis.localStorage; + +function restoreLocalStorage(): void { + Object.defineProperty(globalThis, 'localStorage', { + value: realLocalStorage, + writable: true, + configurable: true, + }); +} + +/** + * Shape: Section(10) → Row(11) → Column(12) → Element(13). Renders a DOM + * that mirrors the real editor's nesting (outer wrapper with data-node-id + * on each level, inner header testid, descendants inside the wrapper) so + * `closest('[data-node-id]')` resolution exercises the same traversal as + * production. + */ +function renderFixture(dragValue: DragContextValue = { activeType: null }): { + elements: Record; + probe: { current: ReturnType | null }; + unmount: () => void; +} { + const element = createSimpleElement({ id: 13 }); + const column = createColumnNode({ id: 12, children: [element] }); + const row = createRowNode({ id: 11, children: [column] }); + const section = createSectionNode({ id: 10, children: [row] }); + const tree = createTreeApiResponse({ sections: [section] }); + + const probe: { current: ReturnType | null } = { current: null }; + + function Probe(): null { + probe.current = useInspect(); + return null; + } + + const Wrapper = ({ children }: { children: ReactNode }): React.JSX.Element => ( + + + + + {children} + + + ); + + const { unmount } = render( + +
+
section header
+
+
row header
+
+
column header
+
+
element header
+
+
+
+
+
, + ); + + const byTestId = (key: string): HTMLElement => { + const el = document.querySelector(`[data-testid="${key}"]`); + if (el === null) throw new Error(`testid ${key} not found`); + return el; + }; + + return { + elements: { + section: byTestId('section'), + sectionHeader: byTestId('section-header'), + row: byTestId('row'), + rowHeader: byTestId('row-header'), + column: byTestId('column'), + columnHeader: byTestId('column-header'), + element: byTestId('element'), + elementHeader: byTestId('element-header'), + }, + probe, + unmount, + }; +} + +function fireMouseMove(target: HTMLElement): void { + act(() => { + target.dispatchEvent(new MouseEvent('mousemove', { bubbles: true })); + }); +} + +describe('InspectHoverDelegate', () => { + beforeEach(() => { + installMockLocalStorage(); + localStorage.setItem('grid:inspect-mode', 'true'); + }); + + afterEach(() => { + cleanup(); + restoreLocalStorage(); + }); + + it('renders nothing', () => { + const { elements, unmount } = renderFixture(); + // Delegate itself contributes no DOM — fixture DOM count is stable. + expect(elements.section.parentElement).not.toBeNull(); + unmount(); + }); + + it('does not drive hover state when inspect is disabled', () => { + localStorage.setItem('grid:inspect-mode', 'false'); + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.columnHeader); + + expect(probe.current?.hover).toBeNull(); + unmount(); + }); + + it('mousemove over a block updates editor hover to that block', () => { + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.columnHeader); + + expect(probe.current?.hover).toMatchObject({ source: 'editor', id: 12 }); + unmount(); + }); + + it('mousemove resolves to the INNERMOST [data-node-id] ancestor', () => { + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.elementHeader); + + // element-header sits inside element (13), which sits inside column (12), + // row (11), and section (10). Innermost id wins. + expect(probe.current?.hover).toMatchObject({ source: 'editor', id: 13 }); + unmount(); + }); + + it('moving the cursor from a child block back up to its parent updates hover to the parent', () => { + // This is the regression scenario: per-element React onMouseEnter on the + // column wrapper never refires when the cursor re-enters the wrapper + // from a descendant, leaving the preview stuck on the child highlight. + // The delegate resolves `closest('[data-node-id]')` on every move, so + // moving from element-header → column-header correctly updates hover + // from element (13) to column (12). + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.elementHeader); + expect(probe.current?.hover).toMatchObject({ source: 'editor', id: 13 }); + + fireMouseMove(elements.columnHeader); + expect(probe.current?.hover).toMatchObject({ source: 'editor', id: 12 }); + unmount(); + }); + + it('mousemove outside any block clears hover', () => { + const { elements, probe, unmount } = renderFixture(); + + // Seed a hover first. + fireMouseMove(elements.columnHeader); + expect(probe.current?.hover).not.toBeNull(); + + // Create a detached element with no [data-node-id] ancestor. + const outside = document.createElement('div'); + document.body.appendChild(outside); + fireMouseMove(outside); + + expect(probe.current?.hover).toBeNull(); + outside.remove(); + unmount(); + }); + + it('suppresses hover updates while a drag is active', () => { + const { elements, probe, unmount } = renderFixture({ activeType: 'column' }); + + fireMouseMove(elements.columnHeader); + + expect(probe.current?.hover).toBeNull(); + unmount(); + }); + + it('leaving the document clears hover', () => { + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.columnHeader); + expect(probe.current?.hover).not.toBeNull(); + + act(() => { + document.documentElement.dispatchEvent(new MouseEvent('mouseleave', { bubbles: false })); + }); + + expect(probe.current?.hover).toBeNull(); + unmount(); + }); + + it('disabling inspect while hovered clears the hover', () => { + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.columnHeader); + expect(probe.current?.hover).not.toBeNull(); + + act(() => { + probe.current?.setEnabled(false); + }); + + expect(probe.current?.hover).toBeNull(); + unmount(); + }); + + it('deduplicates moves over the same block (no redundant state changes)', () => { + const { elements, probe, unmount } = renderFixture(); + + fireMouseMove(elements.columnHeader); + const firstHover = probe.current?.hover; + + // Second move inside the same block should not produce a different + // hover reference — the delegate short-circuits via its lastIdRef. + fireMouseMove(elements.column); + expect(probe.current?.hover).toBe(firstHover); + unmount(); + }); +}); diff --git a/client/src/inspect/InspectHoverDelegate.tsx b/client/src/inspect/InspectHoverDelegate.tsx new file mode 100644 index 00000000..4dde7120 --- /dev/null +++ b/client/src/inspect/InspectHoverDelegate.tsx @@ -0,0 +1,118 @@ +import { useEffect, useMemo, useRef } from 'react'; +import { useDragContext } from '@/hooks/useDragAndDrop'; +import type { ElementNode, TreeApiResponse } from '@/types/elements'; +import { useInspect } from './InspectContext'; + +/** + * Walks the tree and returns a flat `id → ElementNode` map. Container + * nodes (section/row/column) expose their children on `children`; leaves + * don't. One pass per tree update — memoised in the consumer. + */ +function buildNodeById(tree: TreeApiResponse): Map { + const byId = new Map(); + + const walk = (node: ElementNode): void => { + byId.set(node.id, node); + const children = 'children' in node ? node.children : null; + if (children !== null && children !== undefined) { + for (const child of children) walk(child); + } + }; + + for (const root of tree.nodes) walk(root); + return byId; +} + +interface Props { + readonly tree: TreeApiResponse; +} + +/** + * Single delegated `mousemove` listener that resolves the innermost block + * under the cursor via `event.target.closest('[data-node-id]')` and drives + * {@link useInspect}'s `setEditorHover`. Replaces per-element React + * `onMouseEnter` / `onMouseLeave` handlers, which don't compose correctly + * across nested bound elements: React only fires `onMouseEnter` on the + * outer wrapper when the cursor enters from OUTSIDE the wrapper, so moving + * the cursor back from a child (ElementCard) to the column's own header + * never refires the column's handler and the preview stayed stuck on the + * child's highlight. + * + * A single delegated listener mirrors how the preview-side inspector already + * works (`client/src/preview/inspector.ts`): on every mousemove, resolve the + * innermost `[data-node-id]` ancestor and drive state from that. + * + * Must be rendered inside {@link DragContext.Provider} so drag suppression + * reflects live drag state; the consumer (`GridEditor`) mounts one instance + * per render branch (editable and readonly). + */ +export function InspectHoverDelegate({ tree }: Props): null { + const { enabled, setEditorHover } = useInspect(); + const { activeType } = useDragContext(); + + const nodeById = useMemo(() => buildNodeById(tree), [tree]); + + const lastIdRef = useRef(null); + // `activeType` is captured in a ref so the document-level listener can + // read fresh drag state without re-binding on every drag state change. + const activeTypeRef = useRef(activeType); + useEffect(() => { + activeTypeRef.current = activeType; + }, [activeType]); + + useEffect(() => { + if (!enabled) { + if (lastIdRef.current !== null) { + lastIdRef.current = null; + setEditorHover(null); + } + return; + } + + const onMove = (event: MouseEvent): void => { + if (activeTypeRef.current !== null) return; + + const rawTarget = event.target; + const target = rawTarget instanceof Element ? rawTarget : null; + const el = target?.closest('[data-node-id]') ?? null; + + if (el === null) { + if (lastIdRef.current !== null) { + lastIdRef.current = null; + setEditorHover(null); + } + return; + } + + const parsed = Number.parseInt(el.dataset.nodeId ?? '', 10); + if (!Number.isFinite(parsed) || parsed === lastIdRef.current) return; + + const node = nodeById.get(parsed); + if (node === undefined) return; + + lastIdRef.current = parsed; + setEditorHover(node); + }; + + const onLeaveDocument = (): void => { + if (lastIdRef.current !== null) { + lastIdRef.current = null; + setEditorHover(null); + } + }; + + document.addEventListener('mousemove', onMove, { passive: true }); + document.documentElement.addEventListener('mouseleave', onLeaveDocument); + + return () => { + document.removeEventListener('mousemove', onMove); + document.documentElement.removeEventListener('mouseleave', onLeaveDocument); + if (lastIdRef.current !== null) { + lastIdRef.current = null; + setEditorHover(null); + } + }; + }, [enabled, nodeById, setEditorHover]); + + return null; +} diff --git a/client/src/inspect/InspectOverlay.test.tsx b/client/src/inspect/InspectOverlay.test.tsx new file mode 100644 index 00000000..00e62b65 --- /dev/null +++ b/client/src/inspect/InspectOverlay.test.tsx @@ -0,0 +1,250 @@ +import { act, render, screen } from '@testing-library/react'; +import type { ReactNode } from 'react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { createSimpleElement } from '@/testing/factories'; +import { installMockLocalStorage } from '@/testing/mockLocalStorage'; +import type { PathSegment } from './ancestry'; +import { InspectProvider, useInspect } from './InspectContext'; +import { InspectOverlay } from './InspectOverlay'; + +const realLocalStorage = globalThis.localStorage; + +function Harness({ + children, + apiRef, +}: { + children: ReactNode; + apiRef: { current: ReturnType | null }; +}): React.JSX.Element { + const api = useInspect(); + apiRef.current = api; + return <>{children}; +} + +function renderOverlay(targetMarkup: ReactNode): { + apiRef: { current: ReturnType | null }; +} { + const apiRef: { current: ReturnType | null } = { current: null }; + render( + + + {targetMarkup} + + + , + ); + return { apiRef }; +} + +/** Shorthand for building a PathSegment — keeps tests terse. */ +function pathSegment(overrides: Partial & { id: number }): PathSegment { + return { + id: overrides.id, + title: overrides.title ?? `Element ${overrides.id}`, + type: overrides.type ?? 'element', + }; +} + +describe('InspectOverlay', () => { + beforeEach(() => { + installMockLocalStorage(); + // Stub getBoundingClientRect so the halo has a deterministic rect. + vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ + top: 200, + left: 100, + width: 300, + height: 60, + right: 400, + bottom: 260, + x: 100, + y: 200, + toJSON: () => ({}), + }); + // Silence "not implemented" warnings from jsdom's scrollIntoView. + HTMLElement.prototype.scrollIntoView = vi.fn(); + }); + afterEach(() => { + vi.restoreAllMocks(); + Object.defineProperty(globalThis, 'localStorage', { + value: realLocalStorage, + writable: true, + configurable: true, + }); + }); + + it('renders nothing when hover is null', () => { + renderOverlay(
); + expect(screen.queryByTestId('inspect-halo')).not.toBeInTheDocument(); + expect(screen.queryByTestId('inspect-breadcrumb')).not.toBeInTheDocument(); + }); + + it('renders nothing for editor-source hover (overlay is preview->editor only)', () => { + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setEditorHover(createSimpleElement({ id: 5 })); + }); + expect(screen.queryByTestId('inspect-halo')).not.toBeInTheDocument(); + expect(screen.queryByTestId('inspect-breadcrumb')).not.toBeInTheDocument(); + }); + + it('renders a solid halo for a direct hit (target is in the editor DOM)', () => { + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover(5, [], [pathSegment({ id: 5, title: 'Hero' })]); + }); + const halo = screen.getByTestId('inspect-halo'); + expect(halo).toHaveClass('inspect-halo'); + expect(halo).not.toHaveClass('inspect-halo--indirect'); + expect(halo).not.toHaveClass('inspect-halo--missing'); + expect(halo).toHaveStyle({ top: '200px', left: '100px', width: '300px', height: '60px' }); + }); + + it('renders a dashed indirect halo when the target is inside a collapsed ancestor', () => { + // Only the outer section is rendered — 99 (the target) is not in the DOM. + // ancestorIds is [outermost, ..., immediate parent] per contract. + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover( + 99, + [1, 3], + [ + pathSegment({ id: 1, title: 'Section 1', type: 'section' }), + pathSegment({ id: 3, title: 'Row 1', type: 'row' }), + pathSegment({ id: 99, title: 'Paragraph', type: 'element' }), + ], + ); + }); + const halo = screen.getByTestId('inspect-halo'); + expect(halo).toHaveClass('inspect-halo--indirect'); + expect(halo).not.toHaveClass('inspect-halo--missing'); + }); + + it('returns null when neither the target nor any ancestor is in the DOM', () => { + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover( + 99, + [1, 2, 3], + [ + pathSegment({ id: 1 }), + pathSegment({ id: 2 }), + pathSegment({ id: 3 }), + pathSegment({ id: 99 }), + ], + ); + }); + expect(screen.queryByTestId('inspect-halo')).not.toBeInTheDocument(); + }); + + it('calls scrollIntoView on the halo target', () => { + const { apiRef } = renderOverlay(
); + const target = document.querySelector('[data-node-id="5"]'); + expect(target).not.toBeNull(); + act(() => { + apiRef.current?.setPreviewHover(5, [], [pathSegment({ id: 5 })]); + }); + expect(target?.scrollIntoView).toHaveBeenCalledWith({ + behavior: 'smooth', + block: 'nearest', + }); + }); + + it('renders the missing variant when the context marks the target as missing', () => { + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover(5, [], [pathSegment({ id: 5 })]); + apiRef.current?.setMissing(true); + }); + const halo = screen.getByTestId('inspect-halo'); + expect(halo).toHaveClass('inspect-halo--missing'); + }); + + describe('breadcrumb', () => { + it('renders the full Section › Row › Column › Element trail', () => { + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover( + 5, + [1, 2, 3], + [ + pathSegment({ id: 1, title: 'Section 1', type: 'section' }), + pathSegment({ id: 2, title: 'Row 1', type: 'row' }), + pathSegment({ id: 3, title: 'Column 2', type: 'column' }), + pathSegment({ id: 5, title: 'Content element 1', type: 'element' }), + ], + ); + }); + const breadcrumb = screen.getByTestId('inspect-breadcrumb'); + expect(breadcrumb).toHaveTextContent( + /Section 1\s*›\s*Row 1\s*›\s*Column 2\s*›\s*Content element 1/, + ); + }); + + it('dims segments whose containers are collapsed (not in the editor DOM)', () => { + // Only Section 1 is rendered — Row and Column aren't in the DOM because + // Section 1 is "collapsed". The target's direct block is also missing. + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover( + 99, + [1, 2, 3], + [ + pathSegment({ id: 1, title: 'Section 1', type: 'section' }), + pathSegment({ id: 2, title: 'Row 1', type: 'row' }), + pathSegment({ id: 3, title: 'Column 2', type: 'column' }), + pathSegment({ id: 99, title: 'Content element 1', type: 'element' }), + ], + ); + }); + const breadcrumb = screen.getByTestId('inspect-breadcrumb'); + // Section 1 is visible — no hidden attribute. + expect(breadcrumb.querySelector('[data-segment-type="section"]')).not.toHaveAttribute( + 'data-segment-hidden', + ); + // Row, Column, and the target are hidden (collapsed ancestors). + expect(breadcrumb.querySelector('[data-segment-type="row"]')).toHaveAttribute( + 'data-segment-hidden', + 'true', + ); + expect(breadcrumb.querySelector('[data-segment-type="column"]')).toHaveAttribute( + 'data-segment-hidden', + 'true', + ); + expect(breadcrumb.querySelector('[data-segment-type="element"]')).toHaveAttribute( + 'data-segment-hidden', + 'true', + ); + }); + + it('marks the final segment as the target', () => { + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover( + 5, + [1], + [ + pathSegment({ id: 1, title: 'Section 1', type: 'section' }), + pathSegment({ id: 5, title: 'Hero paragraph', type: 'element' }), + ], + ); + }); + const breadcrumb = screen.getByTestId('inspect-breadcrumb'); + const segments = breadcrumb.querySelectorAll('[data-segment-type]'); + expect(segments).toHaveLength(2); + expect(segments[0]).not.toHaveAttribute('data-segment-target'); + expect(segments[1]).toHaveAttribute('data-segment-target', 'true'); + }); + + it('renders no breadcrumb when path is empty even if ancestorIds resolve a halo', () => { + // Defensive: an empty path (e.g. id not found in tree when setPreviewHover + // was called) should not produce a bogus breadcrumb. The halo still + // renders via ancestorIds fallback — path and ancestorIds are separate. + const { apiRef } = renderOverlay(
); + act(() => { + apiRef.current?.setPreviewHover(99, [1], []); + }); + expect(screen.getByTestId('inspect-halo')).toBeInTheDocument(); + expect(screen.queryByTestId('inspect-breadcrumb')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/client/src/inspect/InspectOverlay.tsx b/client/src/inspect/InspectOverlay.tsx new file mode 100644 index 00000000..1cbd7797 --- /dev/null +++ b/client/src/inspect/InspectOverlay.tsx @@ -0,0 +1,233 @@ +import { type CSSProperties, Fragment, useEffect, useState } from 'react'; +import type { PathSegment } from './ancestry'; +import { useInspect } from './InspectContext'; + +const HALO_CLASS = 'inspect-halo'; +const BREADCRUMB_CLASS = 'inspect-breadcrumb'; + +interface Resolution { + target: HTMLElement; + /** True when the halo lands directly on the hovered element's editor block. */ + isDirect: boolean; +} + +/** + * Locate the [data-node-id] element for an id AND verify it's actually + * painted. Collapsed containers keep descendants in the DOM (only CSS hides + * them), so `querySelector` alone would falsely claim they're visible. Use + * the rendered rect as the source of truth: any element whose parent chain + * applies `display:none` / `visibility:hidden` / collapses to zero height + * returns a zero-area rect, which maps to "not visible". + */ +function findVisibleById(id: number): HTMLElement | null { + const el = document.querySelector(`[data-node-id="${id}"]`); + if (el === null) return null; + const rect = el.getBoundingClientRect(); + if (rect.width === 0 && rect.height === 0) return null; + return el; +} + +function resolveHalo(id: number, ancestorIds: readonly number[]): Resolution | null { + const direct = findVisibleById(id); + if (direct !== null) return { target: direct, isDirect: true }; + + // Walk inside-out: halo the closest VISIBLE ancestor. Because we filter on + // rect, this survives both "descendant not rendered at all" and "descendant + // rendered but CSS-hidden by a collapsed container". + for (let i = ancestorIds.length - 1; i >= 0; i--) { + const el = findVisibleById(ancestorIds[i]); + if (el !== null) return { target: el, isDirect: false }; + } + + return null; +} + +/** + * For each path segment, figure out whether it's currently visible in the + * editor DOM (rendered AND not hidden by an ancestor's collapsed state). + * Returns an array in the same order as `path` so the breadcrumb can dim + * segments whose containers collapse them out of view. + */ +function markVisibility(path: readonly PathSegment[]): Array { + return path.map((segment) => ({ + ...segment, + isVisible: findVisibleById(segment.id) !== null, + })); +} + +/** + * Position the breadcrumb relative to the halo target rect. Default: above + * target with an 8px gap. If that would land above the viewport top, flip + * below. Always clamp left/right to the viewport so the breadcrumb never + * hangs off-screen. + */ +function positionBreadcrumb(rect: DOMRect, crumbWidth: number, crumbHeight: number): CSSProperties { + const GAP = 8; + const PADDING = 12; + + const wouldOverflowTop = rect.top - crumbHeight - GAP < 0; + const top = wouldOverflowTop ? rect.bottom + GAP : rect.top - crumbHeight - GAP; + + const rawLeft = rect.left; + const maxLeft = window.innerWidth - crumbWidth - PADDING; + const left = Math.max(PADDING, Math.min(rawLeft, maxLeft)); + + return { + position: 'fixed', + top, + left, + zIndex: 10_000, + }; +} + +/** + * Renders the editor-side overlay for a preview-source hover: + * + * 1. A haloed outline around the matching editor block (solid if the direct + * target is visible; dashed if we had to fall back to a visible ancestor + * because the real target lives inside a collapsed container). + * 2. A floating breadcrumb showing the full `Section › Row › Column › Element` + * trail, with collapsed path segments dimmed so the user sees exactly + * which ancestors they'd need to expand to reach the element. + * + * Editor-source hovers are handled on the preview side — this component only + * renders when the hover originated from the preview iframe. + */ +export function InspectOverlay(): React.JSX.Element | null { + const { hover, missing } = useInspect(); + const [resolution, setResolution] = useState(null); + + useEffect(() => { + if (hover === null || hover.source !== 'preview') { + setResolution(null); + return; + } + + const found = resolveHalo(hover.id, hover.ancestorIds); + setResolution(found); + if (found !== null) { + found.target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + }, [hover]); + + if (resolution === null || hover === null || hover.source !== 'preview') return null; + + const rect = resolution.target.getBoundingClientRect(); + + const haloStyle: CSSProperties = { + position: 'fixed', + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height, + pointerEvents: 'none', + zIndex: 9_999, + }; + + // Halo state: + // - `missing` — preview reported the id doesn't exist; editor-side shows + // greyed/dashed indicator even though we found a proxy. + // - `indirect` — the real target is hidden under a collapsed ancestor. + // - default — direct hit on the hovered element. + const haloModifier = missing + ? `${HALO_CLASS}--missing` + : !resolution.isDirect + ? `${HALO_CLASS}--indirect` + : ''; + const haloClassName = haloModifier !== '' ? `${HALO_CLASS} ${haloModifier}` : HALO_CLASS; + + const visibilityPath = markVisibility(hover.path); + // The last segment in the path is the hovered target. + const lastIndex = visibilityPath.length - 1; + + return ( + +
+ {visibilityPath.length > 0 && ( + + )} + + ); +} + +interface BreadcrumbProps { + readonly segments: ReadonlyArray; + readonly lastIndex: number; + readonly targetRect: DOMRect; + readonly missing: boolean; +} + +/** + * Separate component so we can measure its rendered size AFTER first paint + * and correct the position on the second render. Avoids a flash of + * mis-positioned content. + */ +function BreadcrumbCrumb({ segments, lastIndex, targetRect, missing }: BreadcrumbProps) { + const [ref, setRef] = useState(null); + const [measured, setMeasured] = useState<{ w: number; h: number } | null>(null); + + useEffect(() => { + if (ref === null) return; + const { width, height } = ref.getBoundingClientRect(); + setMeasured({ w: width, h: height }); + }, [ref]); + + const style = + measured === null + ? ({ + position: 'fixed', + top: targetRect.top - 100, + left: targetRect.left, + visibility: 'hidden', + zIndex: 10_000, + } as CSSProperties) + : positionBreadcrumb(targetRect, measured.w, measured.h); + + const rootClass = missing ? `${BREADCRUMB_CLASS} ${BREADCRUMB_CLASS}--missing` : BREADCRUMB_CLASS; + + return ( +
+ {segments.map((segment, index) => { + const isTarget = index === lastIndex; + const classes = [ + `${BREADCRUMB_CLASS}__segment`, + !segment.isVisible ? `${BREADCRUMB_CLASS}__segment--hidden` : '', + isTarget ? `${BREADCRUMB_CLASS}__segment--target` : '', + ] + .filter((c) => c !== '') + .join(' '); + + return ( + + {index > 0 && ( + + )} + + {segment.title || segment.type} + + + ); + })} +
+ ); +} diff --git a/client/src/inspect/InspectToggle.test.tsx b/client/src/inspect/InspectToggle.test.tsx new file mode 100644 index 00000000..2c5a9e1a --- /dev/null +++ b/client/src/inspect/InspectToggle.test.tsx @@ -0,0 +1,88 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import type { ReactNode } from 'react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { installMockLocalStorage } from '@/testing/mockLocalStorage'; +import { InspectProvider } from './InspectContext'; +import { InspectToggle } from './InspectToggle'; + +vi.mock('./resolvePreviewIframe', () => ({ + resolvePreviewIframe: vi.fn(() => document.createElement('iframe')), +})); + +const realLocalStorage = globalThis.localStorage; + +function setup(): ReturnType { + const tree: ReactNode = ( + + + + ); + return render(tree); +} + +describe('InspectToggle', () => { + beforeEach(() => { + installMockLocalStorage(); + vi.useFakeTimers(); + }); + afterEach(() => { + vi.useRealTimers(); + Object.defineProperty(globalThis, 'localStorage', { + value: realLocalStorage, + writable: true, + configurable: true, + }); + }); + + it('renders a pressable button with inactive state by default', () => { + setup(); + const button = screen.getByTestId('inspect-toggle'); + expect(button).toHaveAttribute('aria-pressed', 'false'); + expect(button).toHaveTextContent('Inspect'); + }); + + it('toggles on click', () => { + setup(); + const button = screen.getByTestId('inspect-toggle'); + fireEvent.click(button); + expect(button).toHaveAttribute('aria-pressed', 'true'); + expect(button).toHaveClass('inspect-toggle--active'); + fireEvent.click(button); + expect(button).toHaveAttribute('aria-pressed', 'false'); + expect(button).not.toHaveClass('inspect-toggle--active'); + }); + + it('is disabled when the preview iframe is not resolvable at mount', async () => { + const mod = await import('./resolvePreviewIframe'); + vi.mocked(mod.resolvePreviewIframe).mockReturnValueOnce(null); + setup(); + expect(screen.getByTestId('inspect-toggle')).toBeDisabled(); + }); + + it('uses the disabled-state title when no iframe is present', async () => { + const mod = await import('./resolvePreviewIframe'); + vi.mocked(mod.resolvePreviewIframe).mockReturnValueOnce(null); + setup(); + expect(screen.getByTestId('inspect-toggle')).toHaveAttribute( + 'title', + 'Open split-mode preview to use inspect', + ); + }); + + it('uses the active-state title when the iframe is present', () => { + setup(); + expect(screen.getByTestId('inspect-toggle')).toHaveAttribute('title', 'Inspect'); + }); + + it('polls resolvePreviewIframe and enables itself when the iframe arrives', async () => { + const mod = await import('./resolvePreviewIframe'); + // First call at mount returns null, subsequent calls (from the interval) return an iframe + vi.mocked(mod.resolvePreviewIframe).mockReturnValueOnce(null); + setup(); + expect(screen.getByTestId('inspect-toggle')).toBeDisabled(); + + vi.mocked(mod.resolvePreviewIframe).mockReturnValue(document.createElement('iframe')); + await vi.advanceTimersByTimeAsync(1000); + expect(screen.getByTestId('inspect-toggle')).not.toBeDisabled(); + }); +}); diff --git a/client/src/inspect/InspectToggle.tsx b/client/src/inspect/InspectToggle.tsx new file mode 100644 index 00000000..19db5d19 --- /dev/null +++ b/client/src/inspect/InspectToggle.tsx @@ -0,0 +1,49 @@ +import { useEffect, useState } from 'react'; +import { t } from '@/i18n'; +import { useInspect } from './InspectContext'; +import { resolvePreviewIframe } from './resolvePreviewIframe'; + +/** + * Button in the editor header that flips the inspect-mode enabled flag. + * + * The toggle is disabled when no preview iframe is on the page — inspect has + * no target without a preview, so we surface the unavailability instead of + * failing silently. The iframe may appear AFTER this component mounts + * (admin users flip split-mode on/off after opening a page), so we poll on a + * 1s interval to re-evaluate availability. The coarser `MutationObserver` + * path on `InspectBridgeHost` already exists, but a local poll keeps the + * toggle's state independent of that wiring. + */ +const AVAILABILITY_POLL_MS = 1000; + +export function InspectToggle(): React.JSX.Element { + const { enabled, setEnabled } = useInspect(); + const [available, setAvailable] = useState(() => resolvePreviewIframe() !== null); + + useEffect(() => { + const id = window.setInterval(() => { + setAvailable(resolvePreviewIframe() !== null); + }, AVAILABILITY_POLL_MS); + return () => window.clearInterval(id); + }, []); + + const label = t('WeDevelopGrid.InspectToggle.LABEL', 'Inspect'); + const disabledTitle = t( + 'WeDevelopGrid.InspectToggle.DISABLED_TITLE', + 'Open split-mode preview to use inspect', + ); + + return ( + + ); +} diff --git a/client/src/inspect/ancestry.test.ts b/client/src/inspect/ancestry.test.ts new file mode 100644 index 00000000..54d75bd7 --- /dev/null +++ b/client/src/inspect/ancestry.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, it } from 'vitest'; +import type { TreeApiResponse } from '@/types/elements'; +import { + createColumnNode, + createRowNode, + createSectionNode, + createSimpleElement, + createTreeApiResponse, +} from '@/testing/factories'; +import { resolveAncestorIds, resolveAncestorPath } from './ancestry'; + +/** + * Build a Section(10) > Row(11) > Column(12) > Element(13) tree. Using the + * canonical factories guarantees the nodes carry every BaseFields key + * (self/parent/nodeKey/parentKey/blockSchema/etc.) so the test is robust to + * type changes — only the walk semantics are asserted here. + */ +function buildLinearTree(): TreeApiResponse { + const element = createSimpleElement({ id: 13, parent: { type: 'column', id: 12 } }); + const column = createColumnNode({ + id: 12, + parent: { type: 'row', id: 11 }, + children: [element], + }); + const row = createRowNode({ + id: 11, + parent: { type: 'section', id: 10 }, + children: [column], + }); + const section = createSectionNode({ + id: 10, + parent: { type: 'page', id: 1 }, + children: [row], + }); + return createTreeApiResponse({ rootParent: { type: 'page', id: 1 }, sections: [section] }); +} + +describe('resolveAncestorIds', () => { + it('returns outside-in ancestor ids for a leaf', () => { + const tree = buildLinearTree(); + expect(resolveAncestorIds(tree, 13)).toEqual([10, 11, 12]); + }); + + it('returns ancestor ids for an intermediate column', () => { + const tree = buildLinearTree(); + expect(resolveAncestorIds(tree, 12)).toEqual([10, 11]); + }); + + it('returns ancestor ids for an intermediate row', () => { + const tree = buildLinearTree(); + expect(resolveAncestorIds(tree, 11)).toEqual([10]); + }); + + it('returns empty array for a root section', () => { + const tree = buildLinearTree(); + expect(resolveAncestorIds(tree, 10)).toEqual([]); + }); + + it('returns empty array when id is missing from the tree', () => { + const tree = buildLinearTree(); + expect(resolveAncestorIds(tree, 999)).toEqual([]); + }); + + it('walks nested siblings correctly across multiple sections', () => { + // Two sections sharing a page parent; pick the leaf of the second one. + const e1 = createSimpleElement({ id: 200, parent: { type: 'column', id: 150 } }); + const c1 = createColumnNode({ + id: 150, + parent: { type: 'row', id: 120 }, + children: [e1], + }); + const r1 = createRowNode({ + id: 120, + parent: { type: 'section', id: 100 }, + children: [c1], + }); + const s1 = createSectionNode({ + id: 100, + parent: { type: 'page', id: 1 }, + children: [r1], + }); + + const e2 = createSimpleElement({ id: 203, parent: { type: 'column', id: 152 } }); + const c2 = createColumnNode({ + id: 152, + parent: { type: 'row', id: 122 }, + children: [e2], + }); + const r2 = createRowNode({ + id: 122, + parent: { type: 'section', id: 102 }, + children: [c2], + }); + const s2 = createSectionNode({ + id: 102, + parent: { type: 'page', id: 1 }, + children: [r2], + }); + + const tree = createTreeApiResponse({ + rootParent: { type: 'page', id: 1 }, + sections: [s1, s2], + }); + + expect(resolveAncestorIds(tree, 203)).toEqual([102, 122, 152]); + expect(resolveAncestorIds(tree, 200)).toEqual([100, 120, 150]); + }); +}); + +describe('resolveAncestorPath', () => { + it('returns outside-in path including the target as the last segment', () => { + const tree = buildLinearTree(); + const path = resolveAncestorPath(tree, 13); + expect(path.map((s) => s.id)).toEqual([10, 11, 12, 13]); + expect(path.map((s) => s.type)).toEqual(['section', 'row', 'column', 'element']); + }); + + it('carries titles alongside ids so consumers can render a breadcrumb without looking nodes back up', () => { + // Build with explicit titles that a real tree would carry. + const element = createSimpleElement({ + id: 13, + parent: { type: 'column', id: 12 }, + title: 'Hero paragraph', + }); + const column = createColumnNode({ + id: 12, + parent: { type: 'row', id: 11 }, + children: [element], + title: 'Column 2', + }); + const row = createRowNode({ + id: 11, + parent: { type: 'section', id: 10 }, + children: [column], + title: 'Row 1', + }); + const section = createSectionNode({ + id: 10, + parent: { type: 'page', id: 1 }, + children: [row], + title: 'Section 1', + }); + const tree = createTreeApiResponse({ + rootParent: { type: 'page', id: 1 }, + sections: [section], + }); + + expect(resolveAncestorPath(tree, 13).map((s) => s.title)).toEqual([ + 'Section 1', + 'Row 1', + 'Column 2', + 'Hero paragraph', + ]); + }); + + it('returns a single-segment path for a root section (just the section itself)', () => { + const tree = buildLinearTree(); + const path = resolveAncestorPath(tree, 10); + expect(path).toHaveLength(1); + expect(path[0].id).toBe(10); + }); + + it('returns an empty path when the id is missing from the tree', () => { + const tree = buildLinearTree(); + expect(resolveAncestorPath(tree, 999)).toEqual([]); + }); +}); diff --git a/client/src/inspect/ancestry.ts b/client/src/inspect/ancestry.ts new file mode 100644 index 00000000..f6636623 --- /dev/null +++ b/client/src/inspect/ancestry.ts @@ -0,0 +1,103 @@ +import { type ElementNode, isContainerNode, type TreeApiResponse } from '@/types/elements'; +import type { NodeKey, NodeType } from '@/types/identity'; + +/** + * One segment of the ancestor path. Holds everything the editor-side halo + + * breadcrumb need to render the path, without forcing consumers to look the + * node back up in the tree on every render. + */ +export interface PathSegment { + readonly id: number; + readonly title: string; + readonly type: NodeType; +} + +/** + * Walk the tree depth-first and index every node by composite `nodeKey` and + * by numeric `id`. The tree's `nodes` field is a shallow list of root-level + * sections — descendants live under `children` on container nodes, so we + * must recurse to reach every element. + */ +function indexTree(tree: TreeApiResponse): { + byKey: Map; + byId: Map; +} { + const byKey = new Map(); + const byId = new Map(); + + const visit = (node: ElementNode): void => { + byKey.set(node.nodeKey, node); + byId.set(node.id, node); + if (isContainerNode(node) && node.children !== null) { + for (const child of node.children) visit(child); + } + }; + + for (const node of tree.nodes) visit(node); + + return { byKey, byId }; +} + +function nodeToSegment(node: ElementNode): PathSegment { + return { + id: node.id, + title: node.title, + type: node.self.type, + }; +} + +/** + * Resolve the outside-in list of ancestor ids for a node in the tree. + * + * - For a root-level section, returns an empty array — its parent is the page + * (no matching element node). + * - For any nested node, returns `[rootSection, ..., immediateParent]` so the + * preview highlight can draw nested halos/breadcrumbs in document order. + * - Returns an empty array when the id isn't found; callers should treat that + * as "no highlight" rather than as an error. + * + * Traversal uses `parentKey` (`"type-id"`) not bare `parentId` because page + * IDs and element IDs share the same numeric namespace — keying by id alone + * would collide whenever a page and an element coincidentally share an id. + */ +export function resolveAncestorIds(tree: TreeApiResponse, id: number): number[] { + const { byKey, byId } = indexTree(tree); + + const start = byId.get(id); + if (start === undefined) return []; + + const ancestors: number[] = []; + let current: ElementNode = start; + while (true) { + const parent = byKey.get(current.parentKey); + if (parent === undefined) break; // reached the page (no element node) + ancestors.push(parent.id); + current = parent; + } + return ancestors.reverse(); +} + +/** + * Resolve the full ancestor-to-target path for a hovered node. Returns an + * array where index 0 is the outermost ancestor (the root section) and the + * last entry is the hovered node itself. Useful for rendering a breadcrumb + * that shows "Section 1 › Row 1 › Column 2 › Content element 1". + * + * Returns an empty array when the id isn't in the tree. + */ +export function resolveAncestorPath(tree: TreeApiResponse, id: number): PathSegment[] { + const { byKey, byId } = indexTree(tree); + + const start = byId.get(id); + if (start === undefined) return []; + + const path: PathSegment[] = [nodeToSegment(start)]; + let current: ElementNode = start; + while (true) { + const parent = byKey.get(current.parentKey); + if (parent === undefined) break; + path.push(nodeToSegment(parent)); + current = parent; + } + return path.reverse(); +} diff --git a/client/src/inspect/messageBridge.test.ts b/client/src/inspect/messageBridge.test.ts new file mode 100644 index 00000000..d32d89a6 --- /dev/null +++ b/client/src/inspect/messageBridge.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it, vi } from 'vitest'; +import { createMessageBridge, type InboundFromPreview } from './messageBridge'; + +describe('createMessageBridge', () => { + it('sends outbound messages to the iframe with the page origin', () => { + const postMessage = vi.fn(); + const iframe = { contentWindow: { postMessage } } as unknown as HTMLIFrameElement; + const bridge = createMessageBridge(() => iframe); + bridge.send({ type: 'grid-inspect:activate' }); + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:activate' }, + window.location.origin, + ); + }); + + it('sends highlight with id', () => { + const postMessage = vi.fn(); + const iframe = { contentWindow: { postMessage } } as unknown as HTMLIFrameElement; + const bridge = createMessageBridge(() => iframe); + bridge.send({ type: 'grid-inspect:highlight', id: 42 }); + expect(postMessage).toHaveBeenCalledWith( + { type: 'grid-inspect:highlight', id: 42 }, + window.location.origin, + ); + }); + + it('is a no-op when iframe is missing', () => { + const bridge = createMessageBridge(() => null); + expect(() => bridge.send({ type: 'grid-inspect:clear' })).not.toThrow(); + }); + + it('is a no-op when iframe has no contentWindow', () => { + const iframe = { contentWindow: null } as unknown as HTMLIFrameElement; + const bridge = createMessageBridge(() => iframe); + expect(() => bridge.send({ type: 'grid-inspect:clear' })).not.toThrow(); + }); + + it('subscribes to preview messages with origin filter and schema validation', () => { + const iframe = { contentWindow: window } as unknown as HTMLIFrameElement; + const bridge = createMessageBridge(() => iframe); + const received: InboundFromPreview[] = []; + const unsubscribe = bridge.subscribe((m) => received.push(m)); + + // valid + window.dispatchEvent( + new MessageEvent('message', { + origin: window.location.origin, + data: { type: 'grid-inspect:hover', id: 5 }, + }), + ); + // wrong origin + window.dispatchEvent( + new MessageEvent('message', { + origin: 'http://evil', + data: { type: 'grid-inspect:hover', id: 5 }, + }), + ); + // malformed — missing id + window.dispatchEvent( + new MessageEvent('message', { + origin: window.location.origin, + data: { type: 'grid-inspect:hover' }, + }), + ); + + expect(received).toEqual([{ type: 'grid-inspect:hover', id: 5 }]); + unsubscribe(); + }); + + it('delivers ready / unhover / missing messages', () => { + const iframe = { contentWindow: window } as unknown as HTMLIFrameElement; + const bridge = createMessageBridge(() => iframe); + const received: InboundFromPreview[] = []; + const unsubscribe = bridge.subscribe((m) => received.push(m)); + + window.dispatchEvent( + new MessageEvent('message', { + origin: window.location.origin, + data: { type: 'grid-inspect:ready' }, + }), + ); + window.dispatchEvent( + new MessageEvent('message', { + origin: window.location.origin, + data: { type: 'grid-inspect:unhover' }, + }), + ); + window.dispatchEvent( + new MessageEvent('message', { + origin: window.location.origin, + data: { type: 'grid-inspect:missing', id: 7 }, + }), + ); + + expect(received).toEqual([ + { type: 'grid-inspect:ready' }, + { type: 'grid-inspect:unhover' }, + { type: 'grid-inspect:missing', id: 7 }, + ]); + unsubscribe(); + }); + + it('unsubscribe removes the listener', () => { + const iframe = { contentWindow: window } as unknown as HTMLIFrameElement; + const bridge = createMessageBridge(() => iframe); + const received: InboundFromPreview[] = []; + const unsubscribe = bridge.subscribe((m) => received.push(m)); + unsubscribe(); + + window.dispatchEvent( + new MessageEvent('message', { + origin: window.location.origin, + data: { type: 'grid-inspect:ready' }, + }), + ); + expect(received).toEqual([]); + }); +}); diff --git a/client/src/inspect/messageBridge.ts b/client/src/inspect/messageBridge.ts new file mode 100644 index 00000000..6c3f899d --- /dev/null +++ b/client/src/inspect/messageBridge.ts @@ -0,0 +1,38 @@ +import { type InboundFromPreview, inboundMessageSchema, type OutboundToPreview } from './schemas'; + +export type { InboundFromPreview, OutboundToPreview }; + +export interface MessageBridge { + send(message: OutboundToPreview): void; + subscribe(handler: (message: InboundFromPreview) => void): () => void; +} + +/** + * Create a bridge that posts typed messages to the preview iframe and + * subscribes to typed messages coming back. + * + * The iframe is resolved lazily via `resolveIframe` so that sends issued + * before the iframe renders, or after it's been detached and re-attached, + * always target the current element instead of a cached reference. + * + * Inbound messages are filtered by `window.location.origin` and parsed by + * the Zod schema — handlers never see malformed or cross-origin payloads. + */ +export function createMessageBridge(resolveIframe: () => HTMLIFrameElement | null): MessageBridge { + return { + send(message) { + const iframe = resolveIframe(); + iframe?.contentWindow?.postMessage(message, window.location.origin); + }, + subscribe(handler) { + const listener = (event: MessageEvent): void => { + if (event.origin !== window.location.origin) return; + const result = inboundMessageSchema.safeParse(event.data); + if (!result.success) return; + handler(result.data); + }; + window.addEventListener('message', listener); + return () => window.removeEventListener('message', listener); + }, + }; +} diff --git a/client/src/inspect/previewToEditor.test.tsx b/client/src/inspect/previewToEditor.test.tsx new file mode 100644 index 00000000..e870a996 --- /dev/null +++ b/client/src/inspect/previewToEditor.test.tsx @@ -0,0 +1,114 @@ +import { screen, waitFor } from '@testing-library/react'; +import { act } from 'react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { + createColumnNode, + createRowNode, + createSectionNode, + createSimpleElement, + createTreeApiResponse, + resetIdCounter, +} from '@/testing/factories'; +import { mockFetchSuccess } from '@/testing/mockFetch'; +import { renderWithProviders } from '@/testing/renderWithProviders'; +import GridEditor from '@/components/GridEditor/GridEditor'; + +// GridEditor already owns the InspectProvider/BridgeHost/Overlay stack — we +// test the preview -> editor direction end-to-end by dispatching the same +// MessageEvent the real preview bundle would post. +vi.mock('@dnd-kit/sortable', () => ({ + useSortable: () => ({ + attributes: {}, + listeners: {}, + setNodeRef: vi.fn(), + transform: null, + transition: undefined, + isDragging: false, + isOver: false, + }), + SortableContext: ({ children }: { children: React.ReactNode }) => <>{children}, + verticalListSortingStrategy: {}, + horizontalListSortingStrategy: {}, +})); + +vi.mock('@dnd-kit/core', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + DndContext: ({ children }: { children: React.ReactNode }) => <>{children}, + DragOverlay: ({ children }: { children: React.ReactNode }) => <>{children}, + MeasuringStrategy: { Always: 'always' }, + }; +}); + +vi.mock('@/hooks/useDragAndDrop', () => ({ + useDragContext: () => ({ activeType: null }), + DragContext: { + Provider: ({ children }: { children: React.ReactNode }) => <>{children}, + }, + useDragAndDrop: () => ({ + dndContextProps: {}, + dragState: null, + pendingTree: null, + }), +})); + +function dispatchPreviewMessage(data: unknown): void { + window.dispatchEvent(new MessageEvent('message', { origin: window.location.origin, data })); +} + +describe('preview -> editor integration (GridEditor + InspectOverlay)', () => { + beforeEach(() => { + resetIdCounter(); + vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ + top: 10, + left: 20, + width: 100, + height: 50, + right: 120, + bottom: 60, + x: 20, + y: 10, + toJSON: () => ({}), + }); + HTMLElement.prototype.scrollIntoView = vi.fn(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('shows a halo on the editor block whose data-node-id matches the hovered preview element id', async () => { + const element = createSimpleElement({ id: 13, parent: { type: 'column', id: 12 } }); + const column = createColumnNode({ + id: 12, + parent: { type: 'row', id: 11 }, + children: [element], + }); + const row = createRowNode({ id: 11, parent: { type: 'section', id: 10 }, children: [column] }); + const section = createSectionNode({ + id: 10, + parent: { type: 'page', id: 1 }, + children: [row], + }); + const tree = createTreeApiResponse({ + rootParent: { type: 'page', id: 1 }, + sections: [section], + }); + + mockFetchSuccess(tree); + + renderWithProviders(); + + // Wait for the tree to load and the element-card to appear in the DOM — + // this also guarantees InspectBridgeHost and InspectOverlay have mounted. + await waitFor(() => expect(screen.getByTestId('element-card')).toBeInTheDocument()); + + // Fire the exact same message shape the preview bundle posts on hover. + act(() => dispatchPreviewMessage({ type: 'grid-inspect:hover', id: 13 })); + + // The halo should appear, sized from the target's getBoundingClientRect. + const halo = await screen.findByTestId('inspect-halo'); + expect(halo).toHaveClass('inspect-halo'); + }); +}); diff --git a/client/src/inspect/resolvePreviewIframe.test.ts b/client/src/inspect/resolvePreviewIframe.test.ts new file mode 100644 index 00000000..65e7318c --- /dev/null +++ b/client/src/inspect/resolvePreviewIframe.test.ts @@ -0,0 +1,27 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { resolvePreviewIframe } from './resolvePreviewIframe'; + +afterEach(() => { + document.body.innerHTML = ''; +}); + +describe('resolvePreviewIframe', () => { + it('returns the iframe with name=cms-preview-iframe', () => { + document.body.innerHTML = ` + + + `; + const result = resolvePreviewIframe(); + expect(result).not.toBeNull(); + expect(result?.getAttribute('data-testid')).toBe('preview'); + }); + + it('returns null when no preview iframe present', () => { + expect(resolvePreviewIframe()).toBeNull(); + }); + + it('ignores non-matching iframe names', () => { + document.body.innerHTML = ``; + expect(resolvePreviewIframe()).toBeNull(); + }); +}); diff --git a/client/src/inspect/resolvePreviewIframe.ts b/client/src/inspect/resolvePreviewIframe.ts new file mode 100644 index 00000000..a8ec4401 --- /dev/null +++ b/client/src/inspect/resolvePreviewIframe.ts @@ -0,0 +1,11 @@ +/** + * Resolves the SilverStripe CMS preview iframe in the admin. + * + * The preview iframe is rendered by the `silverstripe/admin` preview panel and + * always carries `name="cms-preview-iframe"`. This is the single contract + * between the inspect-mode editor code and the CMS shell — any DOM traversal + * that tries to be clever here will break across admin layouts. + */ +export function resolvePreviewIframe(): HTMLIFrameElement | null { + return document.querySelector('iframe[name="cms-preview-iframe"]'); +} diff --git a/client/src/inspect/schemas.ts b/client/src/inspect/schemas.ts new file mode 100644 index 00000000..9cac6ac4 --- /dev/null +++ b/client/src/inspect/schemas.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; + +/** + * Messages the editor sends TO the preview iframe. + * + * Kept in lockstep with the hand-rolled validator in + * `client/src/preview/messageBridge.ts` — the preview bundle cannot depend + * on Zod for size reasons, so drift here must be caught by paired tests. + */ +export const outboundMessageSchema = z.discriminatedUnion('type', [ + z.object({ type: z.literal('grid-inspect:activate') }), + z.object({ type: z.literal('grid-inspect:deactivate') }), + z.object({ type: z.literal('grid-inspect:highlight'), id: z.number().finite() }), + z.object({ type: z.literal('grid-inspect:clear') }), +]); + +/** + * Messages the editor receives FROM the preview iframe. + * + * Validated at the subscription boundary so downstream consumers only see + * well-formed payloads regardless of what an attacker or buggy extension + * posts into the window. + */ +export const inboundMessageSchema = z.discriminatedUnion('type', [ + z.object({ type: z.literal('grid-inspect:ready') }), + z.object({ type: z.literal('grid-inspect:hover'), id: z.number().finite() }), + z.object({ type: z.literal('grid-inspect:unhover') }), + z.object({ type: z.literal('grid-inspect:missing'), id: z.number().finite() }), +]); + +export type OutboundToPreview = z.infer; +export type InboundFromPreview = z.infer; diff --git a/client/src/inspect/storage.test.ts b/client/src/inspect/storage.test.ts new file mode 100644 index 00000000..57090aae --- /dev/null +++ b/client/src/inspect/storage.test.ts @@ -0,0 +1,104 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { readEnabled, STORAGE_KEY, writeEnabled } from './storage'; + +// jsdom's localStorage under vitest is a Proxy that doesn't expose a usable +// `setItem` / `clear`. Mirror the pattern from useCollapseState.test.tsx: +// swap in a plain Map-backed mock for these tests, restore afterwards. + +interface MockStorage extends Storage { + _store: Map; +} + +function createMockLocalStorage(): MockStorage { + const store = new Map(); + return { + _store: store, + get length() { + return store.size; + }, + clear: () => { + store.clear(); + }, + getItem: (key: string): string | null => store.get(key) ?? null, + setItem: (key: string, value: string): void => { + store.set(key, value); + }, + removeItem: (key: string): void => { + store.delete(key); + }, + key: (index: number): string | null => [...store.keys()][index] ?? null, + }; +} + +const realLocalStorage = globalThis.localStorage; + +function installMockLocalStorage(): MockStorage { + const mock = createMockLocalStorage(); + Object.defineProperty(globalThis, 'localStorage', { + value: mock, + writable: true, + configurable: true, + }); + return mock; +} + +describe('storage', () => { + beforeEach(() => { + installMockLocalStorage(); + }); + + afterEach(() => { + Object.defineProperty(globalThis, 'localStorage', { + value: realLocalStorage, + writable: true, + configurable: true, + }); + }); + + it('returns false when key missing', () => { + expect(readEnabled()).toBe(false); + }); + + it("returns true when 'true' stored", () => { + localStorage.setItem(STORAGE_KEY, 'true'); + expect(readEnabled()).toBe(true); + }); + + it('returns false when value is not a boolean literal', () => { + localStorage.setItem(STORAGE_KEY, 'yes'); + expect(readEnabled()).toBe(false); + }); + + it('writeEnabled persists the literal', () => { + writeEnabled(true); + expect(localStorage.getItem(STORAGE_KEY)).toBe('true'); + writeEnabled(false); + expect(localStorage.getItem(STORAGE_KEY)).toBe('false'); + }); + + it('readEnabled swallows thrown errors and returns false', () => { + Object.defineProperty(globalThis, 'localStorage', { + value: { + getItem: () => { + throw new Error('blocked'); + }, + }, + writable: true, + configurable: true, + }); + expect(readEnabled()).toBe(false); + }); + + it('writeEnabled swallows thrown errors', () => { + Object.defineProperty(globalThis, 'localStorage', { + value: { + setItem: () => { + throw new Error('quota'); + }, + }, + writable: true, + configurable: true, + }); + expect(() => writeEnabled(true)).not.toThrow(); + }); +}); diff --git a/client/src/inspect/storage.ts b/client/src/inspect/storage.ts new file mode 100644 index 00000000..d4154190 --- /dev/null +++ b/client/src/inspect/storage.ts @@ -0,0 +1,27 @@ +/** + * localStorage key for the inspect-mode enabled flag. + * + * Persisted as the literal strings `'true'` or `'false'` so a plain + * string-equality check is enough to hydrate and no JSON parsing is + * needed. Any other value (including absent) resolves to `false`. + */ +export const STORAGE_KEY = 'grid:inspect-mode'; + +export function readEnabled(): boolean { + try { + return localStorage.getItem(STORAGE_KEY) === 'true'; + } catch { + // localStorage access can throw in locked-down browsers (e.g. third-party + // cookie blocking). Degrade to disabled — inspect mode is a progressive + // enhancement, never a hard requirement. + return false; + } +} + +export function writeEnabled(value: boolean): void { + try { + localStorage.setItem(STORAGE_KEY, value ? 'true' : 'false'); + } catch { + // QuotaExceededError / SecurityError — silently ignore. + } +} diff --git a/client/src/inspect/useInspectHoverBinding.test.tsx b/client/src/inspect/useInspectHoverBinding.test.tsx new file mode 100644 index 00000000..6f409c1d --- /dev/null +++ b/client/src/inspect/useInspectHoverBinding.test.tsx @@ -0,0 +1,18 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { createSimpleElement } from '@/testing/factories'; +import { useInspectHoverBinding } from './useInspectHoverBinding'; + +describe('useInspectHoverBinding', () => { + it('returns data-node-id as a string derived from node.id', () => { + const node = createSimpleElement({ id: 42 }); + const { result } = renderHook(() => useInspectHoverBinding(node)); + expect(result.current['data-node-id']).toBe('42'); + }); + + it('stringifies arbitrary numeric ids', () => { + const node = createSimpleElement({ id: 1 }); + const { result } = renderHook(() => useInspectHoverBinding(node)); + expect(result.current['data-node-id']).toBe('1'); + }); +}); diff --git a/client/src/inspect/useInspectHoverBinding.ts b/client/src/inspect/useInspectHoverBinding.ts new file mode 100644 index 00000000..a552daff --- /dev/null +++ b/client/src/inspect/useInspectHoverBinding.ts @@ -0,0 +1,30 @@ +import type { ElementNode } from '@/types/elements'; + +/** + * Attributes returned by {@link useInspectHoverBinding}. Spread directly on + * the root element of a block component. + * + * Only `data-node-id` is attached — actual hover detection is handled by + * {@link InspectHoverDelegate} via a single delegated `mousemove` listener. + * Per-element React `onMouseEnter` / `onMouseLeave` handlers don't compose + * across nested bound elements: a child's enter cancels the parent's pending + * highlight and the parent's enter never refires when the cursor comes back + * from that child. The delegated listener resolves the innermost + * `[data-node-id]` ancestor on every move and drives state from that, + * matching the preview-side inspector's hit-test model. + */ +export interface InspectHoverBindingAttrs { + 'data-node-id': string; +} + +/** + * Per-block binding helper. Kept as a hook (not a plain function) so + * block components have a stable import point should we need to add + * element-local state later (e.g., aria-activedescendant for keyboard + * traversal), without churning every call site. + */ +export function useInspectHoverBinding(node: ElementNode): InspectHoverBindingAttrs { + return { + 'data-node-id': String(node.id), + }; +} diff --git a/client/src/preview/breadcrumb.test.ts b/client/src/preview/breadcrumb.test.ts new file mode 100644 index 00000000..09a59ee3 --- /dev/null +++ b/client/src/preview/breadcrumb.test.ts @@ -0,0 +1,115 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { buildBreadcrumb, positionBreadcrumb, removeBreadcrumb } from './breadcrumb'; + +describe('buildBreadcrumb', () => { + beforeEach(() => { + document.body.innerHTML = ''; + }); + + it('creates a single breadcrumb element appended to body', () => { + const labels = ['Section: Hero', 'Row', 'Column', 'Paragraph']; + const node = buildBreadcrumb(labels); + expect(node.parentElement).toBe(document.body); + expect(node.classList.contains('grid-inspect-breadcrumb')).toBe(true); + expect(node.textContent).toContain('Section: Hero'); + expect(node.textContent).toContain('Paragraph'); + }); + + it('reuses the existing breadcrumb on subsequent calls', () => { + const first = buildBreadcrumb(['a']); + const second = buildBreadcrumb(['b']); + expect(second).toBe(first); + expect(second.textContent).toContain('b'); + expect(second.textContent).not.toContain('a'); + }); +}); + +describe('positionBreadcrumb', () => { + it('positions above target by default', () => { + const node = buildBreadcrumb(['label']); + node.getBoundingClientRect = () => ({ + width: 120, + height: 24, + top: 0, + left: 0, + right: 120, + bottom: 24, + x: 0, + y: 0, + toJSON: () => ({}), + }); + positionBreadcrumb(node, { + top: 200, + left: 300, + bottom: 240, + right: 420, + width: 120, + height: 40, + }); + expect(node.style.top).toBe('168px'); // 200 - 32 + expect(node.style.left).toBe('300px'); + }); + + it('flips below when above would overflow viewport', () => { + const node = buildBreadcrumb(['label']); + node.getBoundingClientRect = () => ({ + width: 120, + height: 24, + top: 0, + left: 0, + right: 120, + bottom: 24, + x: 0, + y: 0, + toJSON: () => ({}), + }); + positionBreadcrumb(node, { + top: 10, + left: 0, + bottom: 50, + right: 120, + width: 120, + height: 40, + }); + expect(node.style.top).toBe('58px'); // 50 + 8 + }); + + it('clamps left to viewport padding', () => { + const node = buildBreadcrumb(['label']); + node.getBoundingClientRect = () => ({ + width: 120, + height: 24, + top: 0, + left: 0, + right: 120, + bottom: 24, + x: 0, + y: 0, + toJSON: () => ({}), + }); + positionBreadcrumb(node, { + top: 200, + left: -50, + bottom: 240, + right: 70, + width: 120, + height: 40, + }); + expect(node.style.left).toBe('8px'); + }); +}); + +describe('removeBreadcrumb', () => { + it('removes breadcrumb from DOM', () => { + const node = buildBreadcrumb(['x']); + expect(node.parentElement).not.toBeNull(); + removeBreadcrumb(); + expect(document.querySelector('.grid-inspect-breadcrumb')).toBeNull(); + }); + + it('is idempotent', () => { + removeBreadcrumb(); + removeBreadcrumb(); + expect(document.querySelector('.grid-inspect-breadcrumb')).toBeNull(); + }); +}); diff --git a/client/src/preview/breadcrumb.ts b/client/src/preview/breadcrumb.ts new file mode 100644 index 00000000..c3af042b --- /dev/null +++ b/client/src/preview/breadcrumb.ts @@ -0,0 +1,43 @@ +const CLASS = 'grid-inspect-breadcrumb'; +const OFFSET_ABOVE = 32; +const OFFSET_BELOW = 8; +const EDGE_PADDING = 8; + +export interface TargetRect { + top: number; + left: number; + bottom: number; + right: number; + width: number; + height: number; +} + +export function buildBreadcrumb(labels: readonly string[]): HTMLDivElement { + let node = document.querySelector(`.${CLASS}`); + if (node === null) { + node = document.createElement('div'); + node.className = CLASS; + document.body.appendChild(node); + } + node.textContent = labels.join(' › '); + return node; +} + +export function positionBreadcrumb(node: HTMLDivElement, target: TargetRect): void { + const crumb = node.getBoundingClientRect(); + const wouldOverflowTop = target.top - OFFSET_ABOVE < 0; + const top = wouldOverflowTop ? target.bottom + OFFSET_BELOW : target.top - OFFSET_ABOVE; + + const rawLeft = target.left; + const maxLeft = window.innerWidth - crumb.width - EDGE_PADDING; + const left = Math.max(EDGE_PADDING, Math.min(rawLeft, maxLeft)); + + node.style.position = 'fixed'; + node.style.top = `${top}px`; + node.style.left = `${left}px`; +} + +export function removeBreadcrumb(): void { + const node = document.querySelector(`.${CLASS}`); + if (node !== null) node.remove(); +} diff --git a/client/src/preview/elements.test.ts b/client/src/preview/elements.test.ts new file mode 100644 index 00000000..09ab4b38 --- /dev/null +++ b/client/src/preview/elements.test.ts @@ -0,0 +1,88 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { getAncestors, getElementId, getElementTitle, hitTest } from './elements'; + +function build(html: string): HTMLElement { + const root = document.createElement('div'); + root.innerHTML = html; + document.body.appendChild(root); + return root; +} + +beforeEach(() => { + // Reset the DOM between tests — duplicate IDs across tests break + // id-based querySelector in jsdom when prior roots remain attached. + document.body.innerHTML = ''; +}); + +describe('hitTest', () => { + it('returns the innermost element with data-grid-element-id', () => { + const root = build(` +
+
+

text

+
+
+ `); + const leaf = root.querySelector('#leaf')!; + expect(hitTest(leaf)?.getAttribute('data-grid-element-id')).toBe('2'); + }); + + it('returns null when no grid ancestor exists', () => { + const root = build(`

text

`); + const leaf = root.querySelector('#leaf')!; + expect(hitTest(leaf)).toBeNull(); + }); + + it('returns the element itself if it has the attribute', () => { + const root = build(`

text

`); + const leaf = root.querySelector('#leaf')!; + expect(hitTest(leaf)?.getAttribute('data-grid-element-id')).toBe('5'); + }); +}); + +describe('getAncestors', () => { + it('returns ancestor IDs outside-in excluding self', () => { + const root = build(` +
+
+

text

+
+
+ `); + const target = root.querySelector('[data-grid-element-id="3"]')!; + expect(getAncestors(target)).toEqual([1, 2]); + }); + + it('returns empty array when element has no grid ancestors', () => { + const root = build(`

t

`); + const target = root.querySelector('[data-grid-element-id="1"]')!; + expect(getAncestors(target)).toEqual([]); + }); +}); + +describe('getElementId / getElementTitle', () => { + it('parses numeric id from data-grid-element-id', () => { + const root = build(`
x
`); + const el = root.querySelector('[data-grid-element-id]')!; + expect(getElementId(el)).toBe(42); + expect(getElementTitle(el)).toBe('Hero'); + }); + + it('returns null for non-numeric ids', () => { + const root = build(`
x
`); + const el = root.querySelector('[data-grid-element-id]')!; + expect(getElementId(el)).toBeNull(); + }); + + it('returns null when title is absent', () => { + const root = build(`
x
`); + const el = root.querySelector('[data-grid-element-id]')!; + expect(getElementTitle(el)).toBeNull(); + }); + + it('returns null when title is an empty string', () => { + const root = build(`
x
`); + const el = root.querySelector('[data-grid-element-id]')!; + expect(getElementTitle(el)).toBeNull(); + }); +}); diff --git a/client/src/preview/elements.ts b/client/src/preview/elements.ts new file mode 100644 index 00000000..cb66ed3d --- /dev/null +++ b/client/src/preview/elements.ts @@ -0,0 +1,33 @@ +const ATTR_ID = 'data-grid-element-id'; +const ATTR_TITLE = 'data-grid-element-title'; + +export function hitTest(target: Element | null): HTMLElement | null { + if (target === null) return null; + const start = target instanceof HTMLElement ? target : target.parentElement; + return start?.closest(`[${ATTR_ID}]`) ?? null; +} + +export function getElementId(element: HTMLElement): number | null { + const raw = element.getAttribute(ATTR_ID); + if (raw === null) return null; + const parsed = Number.parseInt(raw, 10); + return Number.isFinite(parsed) && String(parsed) === raw ? parsed : null; +} + +export function getElementTitle(element: HTMLElement): string | null { + const value = element.getAttribute(ATTR_TITLE); + return value !== null && value !== '' ? value : null; +} + +export function getAncestors(element: HTMLElement): number[] { + const ancestors: number[] = []; + let current: HTMLElement | null = element.parentElement; + while (current !== null) { + if (current.hasAttribute(ATTR_ID)) { + const id = getElementId(current); + if (id !== null) ancestors.push(id); + } + current = current.parentElement; + } + return ancestors.reverse(); +} diff --git a/client/src/preview/index.test.ts b/client/src/preview/index.test.ts new file mode 100644 index 00000000..41ed99c7 --- /dev/null +++ b/client/src/preview/index.test.ts @@ -0,0 +1,94 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { boot } from './index'; + +interface Posted { + type: string; + [key: string]: unknown; +} + +/** + * Install a stub `window.parent` whose `postMessage` collects outbound calls. + * The preview uses `window.parent.postMessage` for every outbound message, so + * this is sufficient to capture every handshake step. + */ +function stubParent(): { posts: Posted[]; restore: () => void } { + const posts: Posted[] = []; + const originalDescriptor = Object.getOwnPropertyDescriptor(window, 'parent'); + Object.defineProperty(window, 'parent', { + configurable: true, + value: { postMessage: (m: Posted) => posts.push(m) }, + }); + return { + posts, + restore: () => { + if (originalDescriptor !== undefined) { + Object.defineProperty(window, 'parent', originalDescriptor); + } + }, + }; +} + +function dispatchFromParent(data: unknown): void { + window.dispatchEvent(new MessageEvent('message', { origin: window.location.origin, data })); +} + +describe('preview boot handshake', () => { + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('posts `ready` on boot', () => { + const stub = stubParent(); + boot(); + expect(stub.posts).toContainEqual({ type: 'grid-inspect:ready' }); + stub.restore(); + }); + + it('re-posts `ready` whenever the parent sends a control message', () => { + // Regression guard for the preview→editor handshake race: preview.js + // runs during the iframe's HTML parse and posts ready before the editor's + // React-based subscribe effect has wired its listener. Without this + // re-announce, the editor's `readyRef` stays `false` forever and any + // later toggle-on leaves `activate` stuck in the queue. + const stub = stubParent(); + boot(); + // Drop the boot-time ready so assertions only see responses. + stub.posts.length = 0; + + dispatchFromParent({ type: 'grid-inspect:deactivate' }); + expect(stub.posts).toContainEqual({ type: 'grid-inspect:ready' }); + + stub.posts.length = 0; + dispatchFromParent({ type: 'grid-inspect:activate' }); + expect(stub.posts).toContainEqual({ type: 'grid-inspect:ready' }); + + stub.posts.length = 0; + dispatchFromParent({ type: 'grid-inspect:highlight', id: 5 }); + expect(stub.posts).toContainEqual({ type: 'grid-inspect:ready' }); + + stub.posts.length = 0; + dispatchFromParent({ type: 'grid-inspect:clear' }); + expect(stub.posts).toContainEqual({ type: 'grid-inspect:ready' }); + + stub.restore(); + }); + + it('ignores malformed or cross-origin messages (no ready echo, no handler run)', () => { + const stub = stubParent(); + boot(); + stub.posts.length = 0; + + // Wrong origin — discarded by subscribeToParent's filter. + window.dispatchEvent( + new MessageEvent('message', { + origin: 'https://evil.example', + data: { type: 'grid-inspect:activate' }, + }), + ); + // Malformed — fails isInboundMessage. + dispatchFromParent({ type: 'grid-inspect:nope' }); + + expect(stub.posts).toEqual([]); + stub.restore(); + }); +}); diff --git a/client/src/preview/index.ts b/client/src/preview/index.ts new file mode 100644 index 00000000..1fa1ca12 --- /dev/null +++ b/client/src/preview/index.ts @@ -0,0 +1,50 @@ +import './styles.css'; +import { activate, clear, createInspector, deactivate, highlight } from './inspector'; +import { postToParent, subscribeToParent } from './messageBridge'; + +if (typeof window !== 'undefined' && window !== window.top) { + boot(); +} + +// Exported for direct unit testing. The real runtime gate is above — in tests, +// `window === window.top`, so calling `boot()` by hand is the only way to +// exercise the handshake. +export function boot(): void { + const inspector = createInspector(); + + const unsubscribe = subscribeToParent(window.location.origin, (message) => { + // Echo `ready` on every inbound message from the parent. The iframe's + // boot-time `ready` can arrive before the editor's InspectBridgeHost has + // wired its subscribe listener (preview.js runs during parse, while the + // parent's `useEffect` subscribe is deferred to the commit phase). When + // that happens, the editor's `readyRef` stays `false` indefinitely, so a + // later toggle-on queues `activate` but never flushes. Piggy-backing a + // `ready` response on every control message closes that race without a + // dedicated handshake: the editor's first outbound (`deactivate` on + // mount, or `activate` on toggle) triggers a fresh `ready` the parent + // receives on its now-live subscription. + postToParent({ type: 'grid-inspect:ready' }); + + switch (message.type) { + case 'grid-inspect:activate': + activate(inspector); + return; + case 'grid-inspect:deactivate': + deactivate(inspector); + return; + case 'grid-inspect:highlight': + highlight(inspector, message.id); + return; + case 'grid-inspect:clear': + clear(inspector); + return; + } + }); + + postToParent({ type: 'grid-inspect:ready' }); + + window.addEventListener('unload', () => { + unsubscribe(); + inspector.destroy(); + }); +} diff --git a/client/src/preview/inspector.test.ts b/client/src/preview/inspector.test.ts new file mode 100644 index 00000000..c67ad92d --- /dev/null +++ b/client/src/preview/inspector.test.ts @@ -0,0 +1,145 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { activate, clear, createInspector, deactivate, highlight } from './inspector'; + +interface Posted { + type: string; + [key: string]: unknown; +} + +function build(html: string): HTMLElement { + document.body.innerHTML = html; + return document.body; +} + +function collectPosts(): { posts: Posted[]; restore: () => void } { + const posts: Posted[] = []; + const original = window.parent; + Object.defineProperty(window, 'parent', { + configurable: true, + value: { postMessage: (m: Posted) => posts.push(m) }, + }); + return { + posts, + restore: () => { + Object.defineProperty(window, 'parent', { configurable: true, value: original }); + }, + }; +} + +afterEach(() => { + document.body.innerHTML = ''; +}); + +describe('createInspector', () => { + it('does not bind mousemove until activate()', () => { + build(`
x
`); + const inspector = createInspector(); + const capture = collectPosts(); + document.dispatchEvent(new MouseEvent('mousemove', { bubbles: true })); + expect(capture.posts).toHaveLength(0); + capture.restore(); + inspector.destroy(); + }); + + it('activate() wires hover detection and posts hover on grid-element entry', () => { + build(`
inner
`); + const inspector = createInspector(); + const capture = collectPosts(); + activate(inspector); + + const target = document.getElementById('t')!; + target.dispatchEvent(new MouseEvent('mousemove', { bubbles: true })); + inspector.flush(); // test-only helper to drain rAF throttle + + expect(capture.posts).toContainEqual({ type: 'grid-inspect:hover', id: 7 }); + capture.restore(); + inspector.destroy(); + }); + + it('highlight() adds target class and clear() removes it', () => { + build(`
x
`); + const el = document.getElementById('t')!; + // jsdom returns an all-zero rect, which our missing-guard treats as detached. + // Stub a non-zero rect so highlight() takes the happy path. jsdom also + // doesn't implement scrollIntoView — stub it to a no-op. + el.getBoundingClientRect = () => + ({ + top: 0, + left: 0, + right: 10, + bottom: 10, + width: 10, + height: 10, + x: 0, + y: 0, + toJSON: () => ({}), + }) as DOMRect; + el.scrollIntoView = () => undefined; + const inspector = createInspector(); + highlight(inspector, 42); + expect(el.classList.contains('grid-inspect-target')).toBe(true); + clear(inspector); + expect(el.classList.contains('grid-inspect-target')).toBe(false); + inspector.destroy(); + }); + + it('highlight() posts missing when id is not in the DOM', () => { + build(`
no grid elements
`); + const inspector = createInspector(); + const capture = collectPosts(); + highlight(inspector, 99); + expect(capture.posts).toContainEqual({ type: 'grid-inspect:missing', id: 99 }); + capture.restore(); + inspector.destroy(); + }); + + it('mouseleave on document element posts unhover and clears highlight', () => { + build(`
x
`); + const inspector = createInspector(); + const target = document.getElementById('t')!; + target.getBoundingClientRect = () => + ({ + top: 0, + left: 0, + right: 10, + bottom: 10, + width: 10, + height: 10, + x: 0, + y: 0, + toJSON: () => ({}), + }) as DOMRect; + + activate(inspector); + const capture = collectPosts(); + + // Simulate a real user hover so currentHover is set before leaving. + target.dispatchEvent(new MouseEvent('mousemove', { bubbles: true })); + inspector.flush(); + expect(capture.posts).toContainEqual({ type: 'grid-inspect:hover', id: 9 }); + expect(target.classList.contains('grid-inspect-target')).toBe(true); + + document.documentElement.dispatchEvent(new MouseEvent('mouseleave', { bubbles: false })); + + expect(target.classList.contains('grid-inspect-target')).toBe(false); + expect(capture.posts).toContainEqual({ type: 'grid-inspect:unhover' }); + capture.restore(); + inspector.destroy(); + }); + + it('deactivate() removes listeners and clears highlight state', () => { + build(`
x
`); + const inspector = createInspector(); + activate(inspector); + highlight(inspector, 1); + deactivate(inspector); + expect(document.getElementById('t')!.classList.contains('grid-inspect-target')).toBe(false); + + const capture = collectPosts(); + document.getElementById('t')!.dispatchEvent(new MouseEvent('mousemove', { bubbles: true })); + inspector.flush(); + expect(capture.posts).toHaveLength(0); + capture.restore(); + inspector.destroy(); + }); +}); diff --git a/client/src/preview/inspector.ts b/client/src/preview/inspector.ts new file mode 100644 index 00000000..53d64166 --- /dev/null +++ b/client/src/preview/inspector.ts @@ -0,0 +1,171 @@ +import { buildBreadcrumb, positionBreadcrumb, removeBreadcrumb } from './breadcrumb'; +import { getElementId, getElementTitle, hitTest } from './elements'; +import { postToParent } from './messageBridge'; + +const CLASS_TARGET = 'grid-inspect-target'; +const CLASS_ANCESTOR = [ + 'grid-inspect-ancestor-1', + 'grid-inspect-ancestor-2', + 'grid-inspect-ancestor-3', +]; + +interface Inspector { + active: boolean; + mutated: Set; + currentHover: HTMLElement | null; + onMouseMove: (event: MouseEvent) => void; + onMouseLeave: () => void; + pendingFrame: number | null; + pendingTarget: HTMLElement | null; + destroy(): void; + flush(): void; // test hook +} + +export function createInspector(): Inspector { + const inspector: Inspector = { + active: false, + mutated: new Set(), + currentHover: null, + pendingFrame: null, + pendingTarget: null, + onMouseMove: () => {}, + onMouseLeave: () => {}, + destroy: () => { + deactivate(inspector); + }, + flush: () => { + if (inspector.pendingFrame !== null) { + cancelAnimationFrame(inspector.pendingFrame); + processHover(inspector, inspector.pendingTarget); + inspector.pendingFrame = null; + inspector.pendingTarget = null; + } + }, + }; + + inspector.onMouseMove = (event: MouseEvent) => { + if (!inspector.active) return; + const target = hitTest(event.target as Element | null); + inspector.pendingTarget = target; + if (inspector.pendingFrame !== null) return; + inspector.pendingFrame = requestAnimationFrame(() => { + inspector.pendingFrame = null; + processHover(inspector, inspector.pendingTarget); + inspector.pendingTarget = null; + }); + }; + + inspector.onMouseLeave = () => { + if (!inspector.active) return; + if (inspector.currentHover !== null) { + clearHighlightDom(inspector); + postToParent({ type: 'grid-inspect:unhover' }); + inspector.currentHover = null; + } + }; + + return inspector; +} + +function processHover(inspector: Inspector, target: HTMLElement | null): void { + if (target === inspector.currentHover) return; + + clearHighlightDom(inspector); + + if (target === null) { + postToParent({ type: 'grid-inspect:unhover' }); + inspector.currentHover = null; + return; + } + + const id = getElementId(target); + if (id === null) return; + + applyHighlight(inspector, target); + inspector.currentHover = target; + postToParent({ type: 'grid-inspect:hover', id }); +} + +function applyHighlight(inspector: Inspector, target: HTMLElement): void { + target.classList.add(CLASS_TARGET); + inspector.mutated.add(target); + + const ancestors = walkAncestors(target).slice(0, 3); + ancestors.forEach((ancestor, index) => { + ancestor.classList.add(CLASS_ANCESTOR[index]); + inspector.mutated.add(ancestor); + }); + + const labels = buildLabels(target, ancestors); + const rect = target.getBoundingClientRect(); + const breadcrumb = buildBreadcrumb(labels); + positionBreadcrumb(breadcrumb, rect); +} + +function walkAncestors(target: HTMLElement): HTMLElement[] { + const acc: HTMLElement[] = []; + let current: HTMLElement | null = target.parentElement; + while (current !== null) { + if (current.hasAttribute('data-grid-element-id')) acc.push(current); + current = current.parentElement; + } + return acc; +} + +function buildLabels(target: HTMLElement, ancestors: readonly HTMLElement[]): string[] { + const labels = [...ancestors].reverse().map((el) => getElementTitle(el) ?? 'Element'); + labels.push(getElementTitle(target) ?? 'Element'); + return labels; +} + +function clearHighlightDom(inspector: Inspector): void { + for (const el of inspector.mutated) { + el.classList.remove(CLASS_TARGET, ...CLASS_ANCESTOR); + } + inspector.mutated.clear(); + removeBreadcrumb(); +} + +export function activate(inspector: Inspector): void { + if (inspector.active) return; + inspector.active = true; + document.addEventListener('mousemove', inspector.onMouseMove, { passive: true }); + document.documentElement.addEventListener('mouseleave', inspector.onMouseLeave); +} + +export function deactivate(inspector: Inspector): void { + if (!inspector.active) return; + inspector.active = false; + document.removeEventListener('mousemove', inspector.onMouseMove); + document.documentElement.removeEventListener('mouseleave', inspector.onMouseLeave); + if (inspector.pendingFrame !== null) { + cancelAnimationFrame(inspector.pendingFrame); + inspector.pendingFrame = null; + inspector.pendingTarget = null; + } + clearHighlightDom(inspector); + inspector.currentHover = null; +} + +export function highlight(inspector: Inspector, id: number): void { + const target = document.querySelector(`[data-grid-element-id="${id}"]`); + if (target === null) { + postToParent({ type: 'grid-inspect:missing', id }); + return; + } + + const rect = target.getBoundingClientRect(); + if (rect.width === 0 && rect.height === 0) { + postToParent({ type: 'grid-inspect:missing', id }); + return; + } + + clearHighlightDom(inspector); + applyHighlight(inspector, target); + target.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); +} + +export function clear(inspector: Inspector): void { + clearHighlightDom(inspector); + inspector.currentHover = null; +} diff --git a/client/src/preview/messageBridge.test.ts b/client/src/preview/messageBridge.test.ts new file mode 100644 index 00000000..6bc31efa --- /dev/null +++ b/client/src/preview/messageBridge.test.ts @@ -0,0 +1,101 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { + type InboundMessage, + isInboundMessage, + type OutboundMessage, + postToParent, + subscribeToParent, +} from './messageBridge'; + +describe('isInboundMessage', () => { + it.each([ + ['activate', { type: 'grid-inspect:activate' }, true], + ['deactivate', { type: 'grid-inspect:deactivate' }, true], + ['highlight with id', { type: 'grid-inspect:highlight', id: 5 }, true], + ['highlight missing id', { type: 'grid-inspect:highlight' }, false], + ['highlight non-numeric id', { type: 'grid-inspect:highlight', id: '5' }, false], + ['clear', { type: 'grid-inspect:clear' }, true], + ['unknown type', { type: 'grid-inspect:wat' }, false], + ['unprefixed', { type: 'highlight' }, false], + ['non-object', 'activate', false], + ['null', null, false], + ])('%s → %s', (_label, input, expected) => { + expect(isInboundMessage(input)).toBe(expected); + }); +}); + +describe('postToParent', () => { + // Preserve the original parent and restore it afterwards — jsdom + // defineProperty overrides can leak across tests otherwise. + const originalParentDescriptor = Object.getOwnPropertyDescriptor(window, 'parent'); + + afterEach(() => { + if (originalParentDescriptor !== undefined) { + Object.defineProperty(window, 'parent', originalParentDescriptor); + } + }); + + it('posts to window.parent with the page origin', () => { + const postMessage = vi.fn(); + Object.defineProperty(window, 'parent', { + configurable: true, + value: { postMessage }, + }); + const originalOrigin = window.location.origin; + + const msg: OutboundMessage = { type: 'grid-inspect:hover', id: 3 }; + postToParent(msg); + + expect(postMessage).toHaveBeenCalledWith(msg, originalOrigin); + }); +}); + +describe('subscribeToParent', () => { + const origin = 'http://localhost'; + let handlerCalls: InboundMessage[]; + + beforeEach(() => { + handlerCalls = []; + }); + + it('invokes handler for same-origin valid messages and ignores others', () => { + const unsubscribe = subscribeToParent(origin, (msg) => handlerCalls.push(msg)); + + // valid: same origin, valid type + window.dispatchEvent( + new MessageEvent('message', { + origin, + data: { type: 'grid-inspect:activate' }, + }), + ); + // rejected: wrong origin + window.dispatchEvent( + new MessageEvent('message', { + origin: 'http://evil.example', + data: { type: 'grid-inspect:activate' }, + }), + ); + // rejected: malformed payload + window.dispatchEvent( + new MessageEvent('message', { + origin, + data: { type: 'grid-inspect:highlight' }, + }), + ); + + expect(handlerCalls).toEqual([{ type: 'grid-inspect:activate' }]); + unsubscribe(); + }); + + it('unsubscribe removes the listener', () => { + const unsubscribe = subscribeToParent(origin, (msg) => handlerCalls.push(msg)); + unsubscribe(); + window.dispatchEvent( + new MessageEvent('message', { + origin, + data: { type: 'grid-inspect:activate' }, + }), + ); + expect(handlerCalls).toEqual([]); + }); +}); diff --git a/client/src/preview/messageBridge.ts b/client/src/preview/messageBridge.ts new file mode 100644 index 00000000..4cfdce5d --- /dev/null +++ b/client/src/preview/messageBridge.ts @@ -0,0 +1,47 @@ +// Hand-rolled validators — keeps the preview bundle free of Zod. +// The shared schema surface is small enough that drift is covered by +// paired tests on editor + preview. + +export type InboundMessage = + | { type: 'grid-inspect:activate' } + | { type: 'grid-inspect:deactivate' } + | { type: 'grid-inspect:highlight'; id: number } + | { type: 'grid-inspect:clear' }; + +export type OutboundMessage = + | { type: 'grid-inspect:ready' } + | { type: 'grid-inspect:hover'; id: number } + | { type: 'grid-inspect:unhover' } + | { type: 'grid-inspect:missing'; id: number }; + +export function isInboundMessage(value: unknown): value is InboundMessage { + if (value === null || typeof value !== 'object') return false; + const record = value as Record; + switch (record.type) { + case 'grid-inspect:activate': + case 'grid-inspect:deactivate': + case 'grid-inspect:clear': + return true; + case 'grid-inspect:highlight': + return typeof record.id === 'number' && Number.isFinite(record.id); + default: + return false; + } +} + +export function postToParent(message: OutboundMessage): void { + window.parent.postMessage(message, window.location.origin); +} + +export function subscribeToParent( + origin: string, + handler: (message: InboundMessage) => void, +): () => void { + const listener = (event: MessageEvent): void => { + if (event.origin !== origin) return; + if (!isInboundMessage(event.data)) return; + handler(event.data); + }; + window.addEventListener('message', listener); + return () => window.removeEventListener('message', listener); +} diff --git a/client/src/preview/styles.css b/client/src/preview/styles.css new file mode 100644 index 00000000..973fcfc3 --- /dev/null +++ b/client/src/preview/styles.css @@ -0,0 +1,34 @@ +/* All rules scoped under .grid-inspect-* to avoid theme collisions. */ + +.grid-inspect-target { + outline: 3px solid #1976d2 !important; + background: rgba(33, 150, 243, 0.12) !important; +} + +.grid-inspect-ancestor-1 { + outline: 2px solid rgba(33, 150, 243, 0.75) !important; + outline-offset: 2px; +} + +.grid-inspect-ancestor-2 { + outline: 2px solid rgba(33, 150, 243, 0.5) !important; + outline-offset: 4px; +} + +.grid-inspect-ancestor-3 { + outline: 2px solid rgba(33, 150, 243, 0.25) !important; + outline-offset: 6px; +} + +.grid-inspect-breadcrumb { + position: fixed; + background: #212121; + color: white; + padding: 4px 10px; + border-radius: 4px; + font: 11px -apple-system, system-ui, sans-serif; + white-space: nowrap; + pointer-events: none; + z-index: 2147483647; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} diff --git a/client/src/styles/bundle.scss b/client/src/styles/bundle.scss index 07591f65..ac7d4c50 100644 --- a/client/src/styles/bundle.scss +++ b/client/src/styles/bundle.scss @@ -11,6 +11,7 @@ @use '../components/RowBlock/RowBlock'; @use '../components/SectionBlock/SectionBlock'; @use '../components/ViewportSwitcher/ViewportSwitcher'; +@use './inspect'; .grid-editor-holder > .grid-editor__container { flex: 0 0 100%; diff --git a/client/src/styles/inspect.scss b/client/src/styles/inspect.scss new file mode 100644 index 00000000..55f709b0 --- /dev/null +++ b/client/src/styles/inspect.scss @@ -0,0 +1,155 @@ +// Styles for the editor-side inspect-mode surface. +// 1. `.grid-editor__header` — horizontal bar grouping `.viewport-switcher` +// and `.inspect-toggle` on a single row. The wrapper was introduced by +// the inspect-mode work (Phase 6); without it the toggle would stack +// under the viewport switcher in CMS layouts. +// 2. `.inspect-toggle` — matches `.viewport-switcher__button` so the two +// controls read as one control cluster (same height, border, padding). +// 3. `.inspect-halo` — overlay rendered by `InspectOverlay` on top of the +// block the preview is currently hovering. Fixed-position and non- +// interactive (`pointer-events: none`) to avoid interfering with the +// underlying editor surface. +// 4. `.inspect-breadcrumb` — floating path indicator rendered alongside the +// halo. Shows the full Section › Row › Column › Element trail; segments +// whose containers are collapsed are dimmed so the admin can see what +// they'd need to expand to reach the hovered element. + +$inspect-halo-color: #0071c4; +$inspect-halo-shadow-color: rgb(0 113 196 / 25%); +$inspect-halo-missing-color: #9a9a9a; +$inspect-halo-indirect-color: #7ba9d8; +$inspect-toggle-active-bg: #0071c4; +$inspect-toggle-border-color: #ccc; +$inspect-toggle-hover-bg: #f0f0f0; +$inspect-toggle-radius: 4px; +$inspect-breadcrumb-bg: #1f2937; +$inspect-breadcrumb-fg: #f8fafc; +$inspect-breadcrumb-dim-fg: rgb(248 250 252 / 45%); +$inspect-breadcrumb-separator-fg: rgb(248 250 252 / 35%); +$inspect-breadcrumb-target-fg: #7dd3fc; +$inspect-breadcrumb-shadow: 0 4px 12px rgb(15 23 42 / 30%); +$inspect-breadcrumb-radius: 6px; + +.grid-editor__header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; +} + +.inspect-toggle { + padding: 6px 16px; + border: 1px solid $inspect-toggle-border-color; + border-radius: $inspect-toggle-radius; + background: #fff; + color: #333; + font-size: 13px; + line-height: 1.4; + cursor: pointer; + transition: + background-color 0.15s ease, + color 0.15s ease; + + &:hover:not(.inspect-toggle--active, :disabled) { + background-color: $inspect-toggle-hover-bg; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &--active { + background-color: $inspect-toggle-active-bg; + border-color: $inspect-toggle-active-bg; + color: #fff; + } +} + +.inspect-halo { + // Positional properties (top/left/width/height/position/zIndex) are set + // inline by `InspectOverlay` because they derive from a runtime rect. + border: 2px solid $inspect-halo-color; + border-radius: 4px; + box-shadow: 0 0 0 2px $inspect-halo-shadow-color; + transition: + border-color 0.15s ease, + box-shadow 0.15s ease; + + // Target sits inside a collapsed ancestor — the halo landed on a proxy. + // Dashed outline + softer colour reads as "not the real target" without + // losing the spatial cue. + &--indirect { + border-style: dashed; + border-color: $inspect-halo-indirect-color; + box-shadow: none; + } + + &--missing { + border-color: $inspect-halo-missing-color; + border-style: dashed; + box-shadow: none; + } +} + +.inspect-breadcrumb { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: $inspect-breadcrumb-bg; + color: $inspect-breadcrumb-fg; + border-radius: $inspect-breadcrumb-radius; + box-shadow: $inspect-breadcrumb-shadow; + pointer-events: none; + max-width: 80vw; + font-size: 12px; + line-height: 1.4; + letter-spacing: 0.01em; + white-space: nowrap; + + &__segment { + // Slight indication that segments are first-class (not just text) so the + // chain reads as discrete steps rather than a run-on string. + display: inline-flex; + align-items: center; + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; + + // Collapsed ancestor — dimmed so the admin sees at a glance which + // segments they'd need to expand. Paired with a small chevron glyph via + // pseudo-element (rendered as "↳" or similar) to make "hidden" explicit + // rather than relying on dimming alone. + &--hidden { + color: $inspect-breadcrumb-dim-fg; + font-style: italic; + + &::after { + content: '\00a0↴'; + margin-left: 2px; + font-style: normal; + font-size: 10px; + opacity: 0.7; + } + } + + // The hovered element itself — promoted with accent colour and weight so + // the eye finds the target in the trail without counting separators. + &--target { + color: $inspect-breadcrumb-target-fg; + font-weight: 600; + } + } + + &__separator { + color: $inspect-breadcrumb-separator-fg; + font-size: 11px; + user-select: none; + } + + &--missing { + background: #374151; + color: $inspect-breadcrumb-dim-fg; + } +} diff --git a/client/src/testing/mockLocalStorage.ts b/client/src/testing/mockLocalStorage.ts new file mode 100644 index 00000000..87aa2cef --- /dev/null +++ b/client/src/testing/mockLocalStorage.ts @@ -0,0 +1,48 @@ +/** + * Map-backed `localStorage` mock for tests. + * + * jsdom's default `localStorage` is a Proxy that doesn't expose usable + * `setItem`/`clear` semantics across property redefinition, so tests that + * exercise storage hydration, writes, and cross-instance sync need a plain + * object with deterministic reads and writes. + */ +export interface MockStorage extends Storage { + readonly _store: Map; +} + +export function createMockLocalStorage(): MockStorage { + const store = new Map(); + return { + _store: store, + get length() { + return store.size; + }, + clear: () => { + store.clear(); + }, + getItem: (key: string): string | null => store.get(key) ?? null, + setItem: (key: string, value: string): void => { + store.set(key, value); + }, + removeItem: (key: string): void => { + store.delete(key); + }, + key: (index: number): string | null => [...store.keys()][index] ?? null, + }; +} + +/** + * Swap `globalThis.localStorage` for a fresh `MockStorage`. Callers are + * responsible for restoring the real value (captured before the swap) in + * `afterEach` — this helper deliberately does not manage teardown so tests + * can compose it with other global setup. + */ +export function installMockLocalStorage(): MockStorage { + const mock = createMockLocalStorage(); + Object.defineProperty(globalThis, 'localStorage', { + value: mock, + writable: true, + configurable: true, + }); + return mock; +} diff --git a/client/src/testing/renderWithProviders.tsx b/client/src/testing/renderWithProviders.tsx index 6d7e3e55..b5821695 100644 --- a/client/src/testing/renderWithProviders.tsx +++ b/client/src/testing/renderWithProviders.tsx @@ -5,6 +5,7 @@ import { vi } from 'vitest'; import { GridEditorProvider } from '@/hooks/GridEditorContext'; import { ViewportProvider } from '@/hooks/ViewportContext'; import { CollapseContext, type CollapseState } from '@/hooks/useCollapseState'; +import { InspectProvider } from '@/inspect/InspectContext'; import type { NodeKey } from '@/types/identity'; export interface RenderOptions { @@ -83,7 +84,9 @@ export function renderWithProviders( - {ui} + + {ui} + , @@ -113,7 +116,9 @@ export function createProviderWrapper(options: RenderOptions = {}) { - {children} + + {children} + diff --git a/client/src/types/styles.d.ts b/client/src/types/styles.d.ts index 5e761afb..8bdc55b1 100644 --- a/client/src/types/styles.d.ts +++ b/client/src/types/styles.d.ts @@ -1 +1,2 @@ declare module '*.scss' {} +declare module '*.css' {} diff --git a/package-lock.json b/package-lock.json index f3f434f8..96a80ef8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "@dnd-kit/utilities": "^3.2.0", "@tanstack/react-query": "^5.99.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zod": "^4.3.6" }, "devDependencies": { "@biomejs/biome": "^2.4.12", @@ -7962,7 +7963,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 26b1b7f5..8dcd9350 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "node": ">=24" }, "scripts": { - "build": "npm run i18n:collect && vite build", + "build": "npm run i18n:collect && vite build && vite build --config vite.preview.config.ts", "dev": "vite build --watch", "test": "vitest run", "test:watch": "vitest", @@ -33,7 +33,8 @@ "@dnd-kit/utilities": "^3.2.0", "@tanstack/react-query": "^5.99.0", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zod": "^4.3.6" }, "devDependencies": { "@biomejs/biome": "^2.4.12", diff --git a/src/Extensions/PreviewInspectorExtension.php b/src/Extensions/PreviewInspectorExtension.php new file mode 100644 index 00000000..00343d3e --- /dev/null +++ b/src/Extensions/PreviewInspectorExtension.php @@ -0,0 +1,46 @@ + + */ +class PreviewInspectorExtension extends Extension +{ + private const string PREVIEW_JS = 'wedevelopnl/silverstripe-grid:client/dist/js/preview.js'; + + private const string PREVIEW_CSS = 'wedevelopnl/silverstripe-grid:client/dist/styles/preview.css'; + + public function onAfterInit(): void + { + if (!$this->isCmsPreviewContext()) { + return; + } + + Requirements::javascript(self::PREVIEW_JS); + Requirements::css(self::PREVIEW_CSS); + } + + private function isCmsPreviewContext(): bool + { + $request = $this->getOwner()->getRequest(); + + // SilverStripe's CMS preview iframe appends CMSPreview=1 to the src. + // Fallback: any explicit draft-stage request is CMS-originated. + return $request->getVar('CMSPreview') === '1' + || $request->getVar('stage') === Versioned::DRAFT; + } +} diff --git a/templates/WeDevelop/Grid/Model/Column_holder.ss b/templates/WeDevelop/Grid/Model/Column_holder.ss index 5190ecfe..ae6e9864 100644 --- a/templates/WeDevelop/Grid/Model/Column_holder.ss +++ b/templates/WeDevelop/Grid/Model/Column_holder.ss @@ -1,3 +1,3 @@ - class="$HolderClasses.ATT"<% end_if %> data-element="$SimpleClassName.LowerCase" id="$Anchor"> + class="$HolderClasses.ATT"<% end_if %> data-element="$SimpleClassName.LowerCase" data-grid-element-id="$ID" data-grid-element-title="$Title.ATT" id="$Anchor"> $Element
diff --git a/templates/WeDevelop/Grid/Model/GridElement_holder.ss b/templates/WeDevelop/Grid/Model/GridElement_holder.ss index cca2728f..b80f4f8c 100644 --- a/templates/WeDevelop/Grid/Model/GridElement_holder.ss +++ b/templates/WeDevelop/Grid/Model/GridElement_holder.ss @@ -1 +1 @@ -$Element +
$Element
diff --git a/templates/WeDevelop/Grid/Model/Row_holder.ss b/templates/WeDevelop/Grid/Model/Row_holder.ss index 5190ecfe..ae6e9864 100644 --- a/templates/WeDevelop/Grid/Model/Row_holder.ss +++ b/templates/WeDevelop/Grid/Model/Row_holder.ss @@ -1,3 +1,3 @@ - class="$HolderClasses.ATT"<% end_if %> data-element="$SimpleClassName.LowerCase" id="$Anchor"> + class="$HolderClasses.ATT"<% end_if %> data-element="$SimpleClassName.LowerCase" data-grid-element-id="$ID" data-grid-element-title="$Title.ATT" id="$Anchor"> $Element
diff --git a/templates/WeDevelop/Grid/Model/Section_holder.ss b/templates/WeDevelop/Grid/Model/Section_holder.ss index 5696e61c..35cfc7e6 100644 --- a/templates/WeDevelop/Grid/Model/Section_holder.ss +++ b/templates/WeDevelop/Grid/Model/Section_holder.ss @@ -1,4 +1,4 @@ - class="$HolderClasses.ATT"<% end_if %> data-element="$SimpleClassName.LowerCase" id="$Anchor"> + class="$HolderClasses.ATT"<% end_if %> data-element="$SimpleClassName.LowerCase" data-grid-element-id="$ID" data-grid-element-title="$Title.ATT" id="$Anchor">
$Element
diff --git a/tests/E2E/Fixture/inspect-mode.yml b/tests/E2E/Fixture/inspect-mode.yml new file mode 100644 index 00000000..36ee829d --- /dev/null +++ b/tests/E2E/Fixture/inspect-mode.yml @@ -0,0 +1,60 @@ +# E2E fixture for inspect-mode journeys. +# +# Structure: +# Page → Hero Section → Hero Row → Hero Col + Text Col (with ContentElement) +# → CTA Section → CTA Row → CTA Col +# +# Top-down ordering: parents before children. FixtureLoader suppresses auto-scaffolding. +# All pages use the 'e2e-' URLSegment prefix for cleanup identification. + +Page: + e2e_inspect_page: + Title: 'E2E Inspect Mode Page' + URLSegment: 'e2e-inspect-mode' + +WeDevelop\Grid\Model\Section: + hero_section: + Title: 'Hero section' + ShowTitle: 1 + TitleTag: h2 + Sort: 1 + Zone: main + Parent: =>Page.e2e_inspect_page + cta_section: + Title: 'Call to action' + ShowTitle: 1 + TitleTag: h2 + Sort: 2 + Zone: main + Parent: =>Page.e2e_inspect_page + +WeDevelop\Grid\Model\Row: + hero_row: + Title: 'Hero row' + Sort: 1 + Parent: =>WeDevelop\Grid\Model\Section.hero_section + cta_row: + Title: 'CTA row' + Sort: 1 + Parent: =>WeDevelop\Grid\Model\Section.cta_section + +WeDevelop\Grid\Model\Column: + hero_col: + Title: 'Hero column' + Sort: 1 + Parent: =>WeDevelop\Grid\Model\Row.hero_row + text_col: + Title: 'Text column' + Sort: 2 + Parent: =>WeDevelop\Grid\Model\Row.hero_row + cta_col: + Title: 'CTA column' + Sort: 1 + Parent: =>WeDevelop\Grid\Model\Row.cta_row + +WeDevelop\Grid\Model\ContentElement: + hero_paragraph: + Title: 'Hero paragraph' + HTML: '

Hero paragraph body content for inspect-mode tests.

' + Sort: 1 + Parent: =>WeDevelop\Grid\Model\Column.text_col diff --git a/tests/E2E/specs/inspect-mode.spec.ts b/tests/E2E/specs/inspect-mode.spec.ts new file mode 100644 index 00000000..c55e2b2a --- /dev/null +++ b/tests/E2E/specs/inspect-mode.spec.ts @@ -0,0 +1,334 @@ +import { expect, test } from '@playwright/test'; +import { startDrag } from '../helpers/drag'; +import { loadFixture, resetFixtures } from '../helpers/fixtures'; +import { enablePreviewMode } from '../helpers/preview'; + +test.describe('Inspect mode', () => { + test.afterAll(async ({ request }) => { + await resetFixtures(request); + }); + + test('admin locates preview-side elements, verifies editor halos, and reloads to confirm persistence', async ({ page }) => { + // The CMS disables split mode when `contentWidth + previewWidth < 1340px`. + // Playwright's default 1280px viewport sits below that threshold; widen + // the viewport so the preview pane is actually visible for hovering. + await page.setViewportSize({ width: 1920, height: 1080 }); + + const fixture = await loadFixture(page.request, 'inspect-mode'); + await page.goto(`/admin/pages/edit/show/${fixture.pageId}`); + await expect(page.getByTestId('grid-editor-loading')).toBeHidden({ timeout: 15_000 }); + await enablePreviewMode(page); + + const previewFrame = page.frameLocator('iframe[name="cms-preview-iframe"]'); + const toggle = page.getByTestId('inspect-toggle'); + const halo = page.getByTestId('inspect-halo'); + + await test.step('enable inspect mode via the toggle', async () => { + // Toggle starts disabled; aria-pressed must flip on click. + await expect(toggle).toBeEnabled(); + await expect(toggle).toHaveAttribute('aria-pressed', 'false'); + await toggle.click(); + await expect(toggle).toHaveAttribute('aria-pressed', 'true'); + }); + + await test.step('hover preview Hero section → editor halo appears and preview breadcrumb shows its title', async () => { + const heroInPreview = previewFrame + .locator('[data-grid-element-title="Hero section"]') + .first(); + await heroInPreview.hover(); + + // Editor-side halo renders, preview-side breadcrumb names the target. + await expect(halo).toBeVisible(); + await expect(previewFrame.locator('.grid-inspect-breadcrumb')).toContainText( + 'Hero section', + ); + // Preview-side target class lands on the hovered section. + await expect(heroInPreview).toHaveClass(/grid-inspect-target/); + }); + + await test.step('hover preview Hero paragraph → halo retargets and breadcrumb lists ancestors', async () => { + const paragraphInPreview = previewFrame + .locator('[data-grid-element-title="Hero paragraph"]') + .first(); + await paragraphInPreview.hover(); + + // Target class moves to the deeper element — previous hovered ancestor + // no longer carries `grid-inspect-target` (inspector clears between hovers). + await expect(paragraphInPreview).toHaveClass(/grid-inspect-target/); + await expect(halo).toBeVisible(); + + // Preview-side breadcrumb (inside the iframe). + const previewBreadcrumb = previewFrame.locator('.grid-inspect-breadcrumb'); + await expect(previewBreadcrumb).toContainText('Hero section'); + await expect(previewBreadcrumb).toContainText('Hero paragraph'); + + // Editor-side breadcrumb — full Section › Row › Column › Element trail + // rendered near the halo so the admin can orient even when the tree + // structure isn't visible in the editor column. + const editorBreadcrumb = page.getByTestId('inspect-breadcrumb'); + await expect(editorBreadcrumb).toBeVisible(); + await expect(editorBreadcrumb).toContainText('Hero section'); + await expect(editorBreadcrumb).toContainText('Hero row'); + await expect(editorBreadcrumb).toContainText('Text column'); + await expect(editorBreadcrumb).toContainText('Hero paragraph'); + }); + + await test.step('collapse Hero section → halo becomes indirect, breadcrumb dims hidden segments', async () => { + // Collapse the Hero section in the editor. The descendant block DOM + // stays mounted (SectionBlock keeps its children rendered; collapse is + // a CSS-only treatment) so the overlay's "visible" check uses rendered + // rect, not mere presence. When a descendant collapses to zero height, + // it counts as hidden — the halo falls back to the visible Section + // proxy and the breadcrumb dims the unreachable segments. + const heroSection = page + .getByTestId('section-block') + .filter({ hasText: 'Hero section' }); + await heroSection.getByTestId('collapse-toggle').first().click(); + + // Wait for the descendant to actually collapse visually (zero height + // vs the CSS class — the latter is not meaningful by itself). + const heroRow = heroSection.getByTestId('row-block').filter({ hasText: 'Hero row' }); + await expect(heroRow).not.toBeVisible(); + + const paragraphInPreview = previewFrame + .locator('[data-grid-element-title="Hero paragraph"]') + .first(); + await paragraphInPreview.hover(); + + // Halo now carries the indirect variant — the real target isn't + // visible in the editor DOM so the overlay paints on the collapsed + // Section proxy. + const haloEl = page.getByTestId('inspect-halo'); + await expect(haloEl).toHaveClass(/inspect-halo--indirect/); + + // Breadcrumb still shows the full trail but marks hidden segments. + const editorBreadcrumb = page.getByTestId('inspect-breadcrumb'); + await expect( + editorBreadcrumb.locator('[data-segment-type="section"]'), + ).not.toHaveAttribute('data-segment-hidden', 'true'); + await expect( + editorBreadcrumb.locator('[data-segment-type="row"]'), + ).toHaveAttribute('data-segment-hidden', 'true'); + await expect( + editorBreadcrumb.locator('[data-segment-type="column"]'), + ).toHaveAttribute('data-segment-hidden', 'true'); + await expect( + editorBreadcrumb.locator('[data-segment-type="element"]'), + ).toHaveAttribute('data-segment-hidden', 'true'); + + // Restore the tree for subsequent steps. + await heroSection.getByTestId('collapse-toggle').first().click(); + await expect(heroRow).toBeVisible(); + }); + + await test.step('reload the edit form → inspect mode persists via localStorage', async () => { + await page.reload({ waitUntil: 'load' }); + await expect(page.getByTestId('grid-editor-loading')).toBeHidden({ timeout: 15_000 }); + + // Toggle state rehydrated from localStorage, preview iframe must still + // be present (enablePreviewMode persists the split-mode preference too). + await expect(toggle).toHaveAttribute('aria-pressed', 'true'); + await expect(previewFrame.locator('body')).toBeAttached({ timeout: 15_000 }); + }); + + await test.step('hover again after reload → preview/editor handshake still wires halo', async () => { + // The InspectBridgeHost sends a deactivate probe on mount when hydrating + // with enabled=true, which the preview echoes as `ready`. Wait for the + // iframe content to exist before hovering so the handshake has a chance + // to complete. + await expect(previewFrame.locator('[data-grid-element-title]').first()).toBeAttached(); + + const ctaInPreview = previewFrame + .locator('[data-grid-element-title="Call to action"]') + .first(); + await ctaInPreview.hover(); + + await expect(halo).toBeVisible(); + await expect(ctaInPreview).toHaveClass(/grid-inspect-target/); + }); + + await test.step('disable inspect → halo and preview target clear', async () => { + await toggle.click(); + await expect(toggle).toHaveAttribute('aria-pressed', 'false'); + + // Halo unmounts (overlay returns null when hover is null or disabled), + // preview clears its target class. + await expect(halo).toBeHidden(); + await expect( + previewFrame.locator('.grid-inspect-target'), + ).toHaveCount(0); + }); + }); + + test('admin verifies spatial placement by hovering editor blocks, restructures via drag, and survives reload', async ({ page }) => { + // Same viewport override: split mode requires content+preview > 1340px. + await page.setViewportSize({ width: 1920, height: 1080 }); + + const fixture = await loadFixture(page.request, 'inspect-mode'); + await page.goto(`/admin/pages/edit/show/${fixture.pageId}`); + await expect(page.getByTestId('grid-editor-loading')).toBeHidden({ timeout: 15_000 }); + await enablePreviewMode(page); + + const previewFrame = page.frameLocator('iframe[name="cms-preview-iframe"]'); + const toggle = page.getByTestId('inspect-toggle'); + + await test.step('enable inspect mode', async () => { + await toggle.click(); + await expect(toggle).toHaveAttribute('aria-pressed', 'true'); + }); + + await test.step('hover editor Hero section → preview highlights matching element', async () => { + // Hover the section's header (not its body) — mouseenter bubbles up + // to the section block's hover binding without also triggering the + // deeper row/column/element bindings. + const heroHeader = page + .getByTestId('section-block') + .filter({ hasText: 'Hero section' }) + .getByTestId('section-header'); + await heroHeader.hover(); + + // The editor→preview hop is debounced by 200ms; the assertion's 2s + // default poll window handles that without an explicit wait. + await expect( + previewFrame.locator('[data-grid-element-title="Hero section"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + }); + + await test.step('hover editor Text column header → preview highlights column', async () => { + const textColumnHeader = page + .getByTestId('column-block') + .filter({ hasText: 'Text column' }) + .getByTestId('column-header'); + await textColumnHeader.hover(); + + await expect( + previewFrame.locator('[data-grid-element-title="Text column"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + }); + + await test.step('hover editor child content element, then move back up to its parent Text column → preview switches target to column', async () => { + // Reproduces the real-user flow: cursor enters a child ElementCard + // (highlights the child), then the user moves up to the parent column + // header to highlight the column itself. The wrapper's React + // onMouseEnter does NOT refire when cursor re-enters the wrapper from + // a descendant, so this path exercises the specific failure mode the + // user reported. + const textColumn = page.getByTestId('column-block').filter({ hasText: 'Text column' }); + const childCard = textColumn.getByTestId('element-card').first(); + const textColumnHeader = textColumn.getByTestId('column-header'); + + await childCard.hover(); + await expect( + previewFrame.locator('[data-grid-element-title="Hero paragraph"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + + await textColumnHeader.hover(); + await expect( + previewFrame.locator('[data-grid-element-title="Text column"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + }); + + await test.step('rapid sibling hover settles on the final target (debounce wins last)', async () => { + // Use section-headers for the same "don't cascade into descendant + // bindings" reason as the previous step. + const heroHeader = page + .getByTestId('section-block') + .filter({ hasText: 'Hero section' }) + .getByTestId('section-header'); + const ctaHeader = page + .getByTestId('section-block') + .filter({ hasText: 'Call to action' }) + .getByTestId('section-header'); + + // Rapid A→B→A→B hover sequence. Debounce collapses the intermediate + // hovers; only the final hover should settle as the preview target. + await heroHeader.hover(); + await ctaHeader.hover(); + await heroHeader.hover(); + await ctaHeader.hover(); + + await expect( + previewFrame.locator('[data-grid-element-title="Call to action"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + // Hero should NOT retain its target class after the final settle. + await expect( + previewFrame.locator('[data-grid-element-title="Hero section"].grid-inspect-target'), + ).toHaveCount(0); + }); + + await test.step('drag a column → inspect hover is suppressed while dragging', async () => { + // `useInspectHoverBinding` disables mouseenter/leave when DragContext + // reports an active drag — so hovering a sibling mid-drag must NOT + // trigger a new preview target. + const heroColumnHeader = page + .getByTestId('column-block') + .filter({ hasText: 'Hero column' }) + .getByTestId('column-header'); + const textColumnHeader = page + .getByTestId('column-block') + .filter({ hasText: 'Text column' }) + .getByTestId('column-header'); + + // Seed the preview with the hero section highlighted, so we can assert + // that mid-drag hovers don't overwrite it. + // Hover the section's header (not its body) — mouseenter bubbles up + // to the section block's hover binding without also triggering the + // deeper row/column/element bindings. + const heroHeader = page + .getByTestId('section-block') + .filter({ hasText: 'Hero section' }) + .getByTestId('section-header'); + await heroHeader.hover(); + await expect( + previewFrame.locator('[data-grid-element-title="Hero section"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + + // Start a drag from the hero column's drag handle toward the text column. + // The drag helper presses, moves past the 8px activation threshold, then + // hovers over the target — all while holding the mouse down. Scope + // to the column-header so we pick the column's own drag handle, not + // one from a child element card. + const dragHandle = heroColumnHeader.getByTestId('drag-handle'); + const dropTarget = textColumnHeader.getByTestId('drag-handle'); + const handle = await startDrag(page, dragHandle, dropTarget); + + // During the drag, the Text column should NOT have become the preview + // target — hover binding is suppressed. The preview still shows Hero + // section from the earlier editor-driven hover. + await expect( + previewFrame.locator('[data-grid-element-title="Text column"].grid-inspect-target'), + ).toHaveCount(0); + + await handle.release(); + }); + + await test.step('reload the edit form → inspect still active and hover still drives preview', async () => { + await page.reload({ waitUntil: 'load' }); + await expect(page.getByTestId('grid-editor-loading')).toBeHidden({ timeout: 15_000 }); + await expect(toggle).toHaveAttribute('aria-pressed', 'true'); + + // Wait for iframe content before hovering so the deactivate-probe + // handshake has fired. + await expect(previewFrame.locator('[data-grid-element-title]').first()).toBeAttached(); + + // Move the cursor to a neutral spot first so the next hover emits a + // fresh mouseenter. After a drag + reload the cursor may still be + // hovering above a block, and without a leave→enter transition the + // mouseenter handler never fires. + await page.mouse.move(0, 0); + + // Hover the section's header (not its body) — mouseenter bubbles up + // to the section block's hover binding without also triggering the + // deeper row/column/element bindings. + const heroHeader = page + .getByTestId('section-block') + .filter({ hasText: 'Hero section' }) + .getByTestId('section-header'); + await heroHeader.hover(); + + await expect( + previewFrame.locator('[data-grid-element-title="Hero section"].grid-inspect-target'), + ).toHaveCount(1, { timeout: 2_000 }); + }); + }); +}); diff --git a/tests/Integration/Extensions/PreviewInspectorExtensionTest.php b/tests/Integration/Extensions/PreviewInspectorExtensionTest.php new file mode 100644 index 00000000..405d19ea --- /dev/null +++ b/tests/Integration/Extensions/PreviewInspectorExtensionTest.php @@ -0,0 +1,70 @@ +setRequest(new HTTPRequest('GET', '/page', ['CMSPreview' => '1'])); + + $extension = new PreviewInspectorExtension(); + $extension->setOwner($controller); + $extension->onAfterInit(); + + $backend = Requirements::backend(); + $this->assertArrayHasKey($this->resolve(self::PREVIEW_JS), $backend->getJavascript()); + $this->assertArrayHasKey($this->resolve(self::PREVIEW_CSS), $backend->getCSS()); + } + + public function testDoesNotRequirePreviewAssetsOnPublicRequest(): void + { + $controller = new Controller(); + $controller->setRequest(new HTTPRequest('GET', '/page')); + + $extension = new PreviewInspectorExtension(); + $extension->setOwner($controller); + $extension->onAfterInit(); + + $backend = Requirements::backend(); + $this->assertArrayNotHasKey($this->resolve(self::PREVIEW_JS), $backend->getJavascript()); + $this->assertArrayNotHasKey($this->resolve(self::PREVIEW_CSS), $backend->getCSS()); + } + + /** + * Requirements::javascript/css resolve module-prefixed paths + * (vendor/package:resource) via ModuleResourceLoader before storage, so we + * compare against the resolved key, not the raw resource identifier. + */ + private function resolve(string $moduleResource): string + { + return ModuleResourceLoader::singleton()->resolvePath($moduleResource); + } +} diff --git a/tests/Integration/Templates/GridHolderMarkupTest.php b/tests/Integration/Templates/GridHolderMarkupTest.php new file mode 100644 index 00000000..4265bf80 --- /dev/null +++ b/tests/Integration/Templates/GridHolderMarkupTest.php @@ -0,0 +1,172 @@ +Title = 'Hero'; + $section->write(); + + $html = $section->forTemplate(); + + $this->assertStringContainsString( + sprintf('data-grid-element-id="%d"', $section->ID), + (string) $html, + ); + $this->assertStringContainsString( + 'data-grid-element-title="Hero"', + (string) $html, + ); + } + + public function testRowHolderEmitsDataGridElementId(): void + { + $section = Section::create(); + $section->Title = 'Hero'; + $section->write(); + // Section::onAfterWrite auto-scaffolds a Row (and a Column). + /** @var Row $row */ + $row = $section->getChildren()->first(); + $row->Title = 'First row'; + $row->write(); + + $html = $row->forTemplate(); + + $this->assertStringContainsString( + sprintf('data-grid-element-id="%d"', $row->ID), + (string) $html, + ); + $this->assertStringContainsString( + 'data-grid-element-title="First row"', + (string) $html, + ); + } + + public function testColumnHolderEmitsDataGridElementId(): void + { + $section = Section::create(); + $section->write(); + /** @var Row $row */ + $row = $section->getChildren()->first(); + /** @var Column $column */ + $column = $row->getChildren()->first(); + $column->Title = 'Left column'; + $column->write(); + + $html = $column->forTemplate(); + + $this->assertStringContainsString( + sprintf('data-grid-element-id="%d"', $column->ID), + (string) $html, + ); + $this->assertStringContainsString( + 'data-grid-element-title="Left column"', + (string) $html, + ); + } + + public function testContentElementForTemplateEmitsDataGridElementId(): void + { + // Content elements extend GridElement and render via the shared + // GridElement_holder.ss template (no ContentElement_holder.ss exists), + // which wraps the inner $Element output in a
. + $content = ContentElement::create(); + $content->Title = 'Hello content'; + $content->HTML = '

Body text

'; + $content->write(); + + $html = (string) $content->forTemplate(); + + $this->assertStringContainsString( + sprintf('data-grid-element-id="%d"', $content->ID), + $html, + ); + $this->assertStringContainsString( + 'data-grid-element-title="Hello content"', + $html, + ); + } + + public function testContentElementHolderIsNotDisplayContents(): void + { + // Regression: a `display:contents` wrapper has no layout box, so its + // getBoundingClientRect() is zero and Preview Inspect Mode cannot + // outline it. The wrapper must remain a real box. + $content = ContentElement::create(); + $content->Title = 'Inspectable'; + $content->HTML = '

Body

'; + $content->write(); + + $html = (string) $content->forTemplate(); + + $this->assertStringNotContainsString('display:contents', $html); + } + + public function testColumnLoopRendersContentElementWithWrapper(): void + { + // Simulates the real rendering flow: a Column template loops over its + // $Elements and emits $Me for each. Each child must be wrapped by the + // GridElement_holder for Preview Inspect Mode to see it. + $section = Section::create(); + $section->write(); + /** @var Row $row */ + $row = $section->getChildren()->first(); + /** @var Column $column */ + $column = $row->getChildren()->first(); + + $content = ContentElement::create(); + $content->Title = 'Inline content'; + $content->HTML = '

Inner

'; + $content->ParentID = $column->ID; + $content->ParentClass = $column::class; + $content->write(); + + $html = (string) $column->forTemplate(); + + $this->assertStringContainsString( + sprintf('data-grid-element-id="%d"', $content->ID), + $html, + ); + } + + public function testGridElementHolderWrapsContentWithDataAttributes(): void + { + // Render a bare GridElement via the shared holder template. + $template = SSViewer::create(['WeDevelop/Grid/Model/GridElement_holder']); + $section = Section::create(); + $section->Title = 'Wrapped'; + $section->write(); + + $html = (string) $template->process($section); + + $this->assertStringContainsString( + sprintf('data-grid-element-id="%d"', $section->ID), + $html, + ); + $this->assertStringContainsString( + 'data-grid-element-title="Wrapped"', + $html, + ); + } +} diff --git a/vite.config.ts b/vite.config.ts index dd1ef212..32ca2106 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -75,6 +75,7 @@ export default defineConfig({ "client/src/bundles/**", "client/src/bridge/**", "client/src/boot/**", + "client/src/preview/index.ts", "client/src/testing/**", "client/src/**/index.ts", "client/src/types/silverstripe.d.ts", diff --git a/vite.preview.config.ts b/vite.preview.config.ts new file mode 100644 index 00000000..b0e5cf2b --- /dev/null +++ b/vite.preview.config.ts @@ -0,0 +1,30 @@ +/// +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vite"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + build: { + outDir: "client/dist", + copyPublicDir: false, + emptyOutDir: false, + lib: { + entry: resolve(__dirname, "client/src/preview/index.ts"), + name: "GridInspectPreview", + formats: ["iife"], + fileName: () => "js/preview.js", + }, + rolldownOptions: { + output: { + assetFileNames: (assetInfo) => { + if (assetInfo.names.some((name) => name.endsWith(".css"))) { + return "styles/preview.css"; + } + return "assets/[name][extname]"; + }, + }, + }, + }, +});