@gemboss/ui 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +285 -0
- package/README.md +638 -3
- package/catalog/editor.html +15 -13
- package/catalog/index.html +5235 -1145
- package/gemboss-ui.css +3 -3
- package/package.json +8 -2
- package/parts/components.css +2 -2
- package/parts/core.css +1 -1
- package/parts/shell.css +2 -2
- package/src/assistant/chat-markdown.ts +7 -4
- package/src/assistant.css +1 -1
- package/src/brand-gem.tsx +19 -0
- package/src/brand.tsx +4 -7
- package/src/chat-plain.js +2 -2
- package/src/editor/controls.tsx +50 -14
- package/src/editor/index.ts +3 -3
- package/src/editor/panel.tsx +10 -4
- package/src/editor/repeater.tsx +113 -27
- package/src/editor/swatch.ts +20 -0
- package/src/editor-plain.js +1 -1
- package/src/editor-repeater.css +1 -1
- package/src/editor.css +1 -1
- package/src/gemboss-field.css +1 -1
- package/src/icons.tsx +11 -0
- package/src/index.ts +12 -1
- package/src/reader-dialog.css +1 -0
- package/src/reader-dialog.tsx +26 -0
- package/src/service-switcher.css +1 -1
- package/src/service-switcher.tsx +108 -31
- package/src/shell.css +1 -1
- package/src/site.css +1 -0
- package/src/site.tsx +50 -0
- package/src/team-chat-data.ts +79 -0
- package/src/team-chat-plain.js +1 -0
- package/src/team-chat.css +1 -0
- package/src/team-chat.tsx +825 -0
- package/src/ui.css +1 -1
package/src/chat-plain.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
(function(O){"use strict";var
|
|
1
|
+
(function(O){"use strict";var w=O.gembossUI;if(!w){O.console&&O.console.error("@gemboss/ui chat-plain.js: load kit-plain.js first (it defines gembossUI)");return}if(w.chat)return;var b={thread:"Conversation",message:"Message",send:"Send",stop:"Stop",attach:"Attach a file",copy:"Copy",copied:"Copied",copyCommand:"Copy command",originalError:"Original error",stopped:"Stopped",steps:"What it is doing",thought:"Thought",thinking:"Thinking",working:"Working",showLess:"Show less",showAll:"Show all {n} lines",openImage:"Open image"},A="http://www.w3.org/2000/svg",k=function(e){return typeof Node<"u"&&e instanceof Node};function l(e,n,r){var t=document.createElement(e);if(n)for(var a in n){var i=n[a];i==null||i===!1||(a==="text"?t.textContent=String(i):a==="className"?t.className=i:t.setAttribute(a,i===!0?"":String(i)))}return R(t,r),t}function R(e,n){return n==null||n===!1?e:Array.isArray(n)?(n.forEach(function(r){R(e,r)}),e):(e.appendChild(k(n)?n:document.createTextNode(String(n))),e)}function M(e,n,r){var t=document.createElementNS(A,"svg");if(t.setAttribute("viewBox",e),t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false"),n.forEach(function(i){var s=document.createElementNS(A,i.tag||"path");for(var o in i)o!=="tag"&&s.setAttribute(o,i[o]);t.appendChild(s)}),r)for(var a in r)t.setAttribute(a,r[a]);return t}var ae=[{host:"admin.gemboss.ai",path:"/api/gemboss/market-image/"},{host:"gemmedia-api-production.up.railway.app",path:"/"},{host:"media.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"gemmedia.ops.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"gemmedia.gemboss.ai",path:"/v1/theme-media/assets/"},{host:"cdn.shopify.com",path:"/"}],q=/[\u0000-\u001f\u007f]/g;function C(e){var n=String(e).replace(q,"").trim();return n?n.charAt(0)==="/"?n.charAt(1)==="/"||n.charAt(1)==="\\"?null:{href:n,internal:!0}:/^(https?:\/\/|mailto:)/i.test(n)?{href:n,internal:!1}:null:null}var D="https://same-origin.invalid",ie=/^\/[^/]+(\/[^/]+)*\/$/;function H(e,n){var r=String(e).replace(q,"").trim(),t;if(r.charAt(0)==="/"){var a=(n||[]).filter(function(i){return!i.host&&ie.test(i.path)});if(!a.length||r.charAt(1)==="/"||r.indexOf("\\")>=0||/%(2f|5c|25)|;/i.test(r))return null;try{t=new URL(r,D)}catch{return null}return t.origin!==D||/\.\.|%2e/i.test(t.pathname)?null:a.some(function(i){return t.pathname.indexOf(i.path)===0})?t.href.slice(D.length):null}try{t=new URL(r)}catch{return null}return t.protocol!=="https:"||t.username||t.password||t.port?null:ae.concat(n||[]).some(function(i){return i.host===t.hostname&&t.pathname.indexOf(i.path)===0})?t.href:null}var T=function(e,n){return e.split(n).length-1};function P(e,n){var r=e.length,t,a,i,s,o=T(e,"("),d=T(e,")"),f=T(e,"["),c=T(e,"]");for(n=n!==!1;n&&r>0;)t=e.charAt(r-1),/[.,:;!?_~'"]/.test(t)?r-=1:t===")"&&o<d?(r-=1,d-=1):t==="]"&&f<c?(r-=1,c-=1):n=!1;if(s=e.slice(0,r),a=C(s),!a||a.internal)return null;try{i=new URL(a.href)}catch{return null}return!/^https?:$/.test(i.protocol)||!i.hostname?null:{href:a.href,rest:e.slice(r)}}var le=/(`([^`]+)`)|(\*\*([^*]+)\*\*)|(\*([^*\n]+)\*)|(!\[([^\]]*)\]\(([^)\s]+)\))|(\[([^\]]+)\]\(([^)\s]+)\))|(<(https?:\/\/[^\s<>]+)>)|(https?:\/\/[^\s<>`"*\u2000-\u206f\u3000-\u9fff\uff00-\uffef]+)/gi;function F(e,n){n=n||{};for(var r=[],t=function(m){if(m){var v=r[r.length-1];v&&v.t==="text"?v.text+=m:r.push({t:"text",text:m})}},a=new RegExp(le.source,"gi"),i=0,s;(s=a.exec(e))!==null;){if(t(e.slice(i,s.index)),s[2]!=null)r.push({t:"code",text:s[2]});else if(s[4]!=null)r.push({t:"bold",text:s[4]});else if(s[6]!=null)r.push({t:"italic",text:s[6]});else if(s[7]!=null){var o=H(s[9],n.imageHosts),d=s[8].trim();if(o)r.push({t:"image",alt:d,src:o});else{var f=C(s[9]);f&&!f.internal?r.push({t:"link",label:d||"image",href:f.href,internal:!1}):t(d)}}else if(s[10]!=null){var c=s[12],p=n.ownLink&&n.ownLink(c)?{href:c,internal:!0,own:!0}:C(c);p?r.push({t:"link",label:s[11],href:p.href,internal:p.internal}):t(s[11])}else{var h=s[15]!=null&&s.index>0&&/[a-z\d]/i.test(e.charAt(s.index-1)),g=h?null:s[14]!=null?P(s[14],!1):P(s[15]);g?(r.push({t:"link",label:g.href,href:g.href,internal:!1}),t(g.rest)):t(s[0])}i=s.index+s[0].length}return t(e.slice(i)),r}var $=function(e){return/^\s*\|.*\|\s*$/.test(e)},se=function(e){return/^\s*\|?[\s:-]*-[-\s:|]*\|?\s*$/.test(e)&&e.indexOf("-")>=0},U=function(e){return e.trim().replace(/^\||\|$/g,"").split("|").map(function(n){return n.trim()})};function K(e){for(var n=String(e??"").split(`
|
|
2
2
|
`),r=[],t=0,a=[],i=function(){a.length&&r.push({t:"para",lines:a.slice()}),a.length=0};t<n.length;){var s=n[t],o;if(o=/^\s*```([\w#+.-]*)\s*$/.exec(s)){i();var d=[];for(t+=1;t<n.length&&!/^\s*```\s*$/.test(n[t]);)d.push(n[t]),t+=1;t+=1,r.push({t:"code",lang:o[1]||"",body:d.join(`
|
|
3
3
|
`)});continue}if(o=/^(#{1,6})\s+(.*)$/.exec(s)){i(),o[2].trim()&&r.push({t:"heading",level:Math.min(o[1].length,3),text:o[2].trim()}),t+=1;continue}if(/^\s*(---|\*\*\*|___)\s*$/.test(s)){i(),r.push({t:"rule"}),t+=1;continue}if($(s)&&t+1<n.length&&se(n[t+1])){i();var f=U(s),c=[];for(t+=2;t<n.length&&$(n[t]);)c.push(U(n[t])),t+=1;r.push({t:"table",head:f,rows:c});continue}if(o=/^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(s)){i();for(var p=/\d/.test(o[1]),h=p?parseInt(o[1],10):void 0,g=[];t<n.length;){var m=/^\s*([-*+]|\d+[.)])\s+(.*)$/.exec(n[t]);if(!m||/\d/.test(m[1])!==p)break;g.push(m[2]),t+=1}var v={t:"list",ordered:p,items:g};h!==void 0&&h!==1&&(v.start=h),r.push(v);continue}if(/^\s*>\s?/.test(s)){i();for(var y=[];t<n.length&&/^\s*>\s?/.test(n[t]);)y.push(n[t].replace(/^\s*>\s?/,"")),t+=1;r.push({t:"quote",lines:y});continue}if(s.trim()===""){i(),t+=1;continue}a.push(s),t+=1}return i(),r}var j=12;function x(e,n,r){return F(e,n).map(function(t){if(t.t==="text")return document.createTextNode(t.text);if(t.t==="code")return l("code",{className:"gemboss-md-code",text:t.text});if(t.t==="bold")return l("strong",0,x(t.text,n,r));if(t.t==="italic")return l("em",0,x(t.text,n,r));if(t.t==="link"){if(n.ownLink&&n.ownLink(t.href)&&n.renderLink)return n.renderLink(t.href,t.label)||document.createTextNode(t.label);var a=l("a",{className:"gemboss-ec-md-link",href:t.href,target:t.internal?null:"_blank",rel:t.internal?null:"noopener noreferrer",text:t.label});return t.internal&&n.onNavigate&&a.addEventListener("click",function(i){i.metaKey||i.ctrlKey||i.shiftKey||(i.preventDefault(),n.onNavigate(t.href))}),a}return l("a",{className:"gemboss-md-imglink",href:t.src,target:"_blank",rel:"noopener noreferrer","aria-label":t.alt?null:r.openImage},l("img",{className:"gemboss-md-img",src:t.src,alt:t.alt,loading:"lazy",referrerpolicy:"no-referrer"}))})}function z(e,n,r){var t=[];return e.forEach(function(a,i){t=t.concat(x(a,n,r)),i<e.length-1&&t.push(l("br"))}),t}function oe(e,n){var r=e.body.split(`
|
|
4
4
|
`),t=r.length>j,a=!1,i=l("code",{text:t?r.slice(0,j).join(`
|
|
5
5
|
`):e.body}),s=l("div",{className:"gemboss-md-codeblock"},[e.lang?l("span",{className:"gemboss-md-codelang",text:e.lang}):null,l("pre",null,i)]);if(t){var o=l("button",{type:"button",className:"gemboss-md-codemore","aria-expanded":"false",text:n.showAll.replace("{n}",String(r.length))});o.addEventListener("click",function(){a=!a,i.textContent=a?e.body:r.slice(0,j).join(`
|
|
6
|
-
`),o.textContent=a?n.showLess:n.showAll.replace("{n}",String(r.length)),o.setAttribute("aria-expanded",String(a))}),s.appendChild(o)}return s}var ue={1:"h3",2:"h4",3:"h5"};function G(e,n,r){n=n||{};var t=Object.assign({},b,r||n.labels||{}),a=document.createDocumentFragment();return K(e).forEach(function(i){var s;i.t==="heading"?s=l(ue[i.level],{className:"gemboss-md-h gemboss-md-h"+i.level},x(i.text,n,t)):i.t==="code"?s=oe(i,t):i.t==="table"?s=l("div",{className:"gemboss-md-tablewrap"},l("table",{className:"gemboss-md-table"},[l("thead",null,l("tr",null,i.head.map(function(o){return l("th",null,x(o,n,t))}))),l("tbody",null,i.rows.map(function(o){return l("tr",null,i.head.map(function(d,f){return l("td",null,x(o[f]||"",n,t))}))}))])):i.t==="list"?s=l(i.ordered?"ol":"ul",{className:"gemboss-md-list",start:i.ordered&&i.start!==void 0?i.start:null},i.items.map(function(o){return l("li",null,x(o,n,t))})):i.t==="quote"?s=l("blockquote",{className:"gemboss-md-quote"},z(i.lines,n,t)):i.t==="rule"?s=l("hr",{className:"gemboss-md-rule"}):s=l("p",{className:"gemboss-md-p"},z(i.lines,n,t)),a.appendChild(s)}),a}function ce(){for(var e=12,n=1.7,r=.5,t=9.6,a=[],i=function(g){return Math.round(g*100)/100},s=function(g,m){return i(g)+" "+i(m)},o=0;o<8;o++){var d=o*Math.PI/4,f=Math.cos(d),c=Math.sin(d),p=-c,h=f;a.push(["M"+s(e+p*n,e+h*n),"L"+s(e+f*t+p*r,e+c*t+h*r),"Q"+s(e+f*(t+.6),e+c*(t+.6))+" "+s(e+f*t-p*r,e+c*t-h*r),"L"+s(e-p*n,e-h*n),"Z"].join(""))}return a.join(" ")}var V={claude:{path:ce(),label:"Claude Code"},codex:{path:"M12 2.6 20.1 7.3V16.7L12 21.4 3.9 16.7V7.3ZM12 6.9 7.6 9.45v5.1L12 17.1l4.4-2.55v-5.1Z",label:"Codex"}};function Y(e,n){var r=Object.prototype.hasOwnProperty.call(V,e)?V[e]:null;if(!r)return null;n=n||{};var t=document.createElementNS(A,"svg");if(t.setAttribute("class",n.className||"gemboss-engine-mark"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","currentColor"),n.decorative)t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false");else{t.setAttribute("role","img"),t.setAttribute("aria-label",n.title||r.label);var a=document.createElementNS(A,"title");a.textContent=n.title||r.label,t.appendChild(a)}var i=document.createElementNS(A,"path");return i.setAttribute("d",r.path),i.setAttribute("fill-rule","nonzero"),t.appendChild(i),t}function Z(e,n){var r=Object.assign({},b,n||{});return!e||!e.length?null:l("ul",{className:"gemboss-narr","aria-label":r.steps},e.map(function(t){var a=t.state||"ok",i;if(t.kind==="thinking")return i=[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u2733"}),a==="running"||!t.detail?l("span",{className:"gemboss-narr-text gemboss-narr-thinking-live"},t.label||r.thinking):l("details",{className:"gemboss-narr-thinking"},[l("summary",null,t.label||r.thought),l("div",{className:"gemboss-narr-thinking-body",text:t.detail})])],l("li",{className:"gemboss-narr-row thinking "+a},i);if(t.kind==="steer")return l("li",{className:"gemboss-narr-row steer "+a},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u276F"}),l("span",{className:"gemboss-narr-text gemboss-narr-steer"},t.label)]);if(t.kind==="trace")return l("li",{className:"gemboss-narr-row trace "+a,title:t.error||null},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="failed"?"\u2715":"\u23FA"}),t.detail?l("details",{className:"gemboss-narr-trace"},[l("summary",null,l("code",null,t.label)),l("div",{className:"gemboss-narr-trace-body"},l("code",{text:t.detail}))]):l("code",{className:"gemboss-narr-text gemboss-narr-trace-call"},t.label)]);var s=t.engine?Y(t.engine):null;return l("li",{className:"gemboss-narr-row "+a,title:t.error||null},[s?l("span",{className:"gemboss-narr-dot gemboss-narr-dot-engine"},s):l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="ok"?"\u2713":a==="failed"?"\u2715":"\xB7"}),l("span",{className:"gemboss-narr-text"},[t.label,t.target?l("span",{className:"gb-chat-target"},[" ",t.target]):null])])}))}function J(e,n){e=e||{};var r=Object.assign({},b,n||{}),t=e.verbs&&e.verbs.length?e.verbs:[r.working],a=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[0]+"\u2026"}),i=l("span",{className:"gemboss-ec-pending-meta","aria-hidden":"true",text:"0s"}),s=l("div",{className:"gemboss-ec-pending",role:"status"},[l("span",{className:"gemboss-assistant-sronly",text:e.label||t[0]}),l("span",{className:"gemboss-ec-pending-star","aria-hidden":"true",text:"\u2733"}),e.compact?null:a,i]),o=e.since||Date.now(),d=!1,f=-1,c=function(){var h=Math.max(0,Math.floor((Date.now()-o)/1e3));i.textContent=(e.tokens>0?e.tokens+" token"+(e.tokens===1?"":"s")+" \xB7 ":"")+h+"s";var g=t.length>1?Math.floor(h/2)%t.length:0;if(g!==f&&!e.compact){f=g;var m=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[f]+"\u2026"});a.replaceWith(m),a=m}};c();var p=setInterval(function(){if(s.isConnected)d=!0;else if(d||Date.now()-o>6e5){clearInterval(p);return}c()},250);return s}function I(e,n,r){r=r||{};var t=document.createDocumentFragment();return!e||!e.length||(r.title&&t.appendChild(l("p",{className:"gb-chat-label"},r.title)),t.appendChild(l("div",{className:"gemboss-ec-starters"},e.map(function(a){var i=l("button",{type:"button",className:"gemboss-ec-starter gb-chat-starter"},[a.hint?l("span",{className:"gb-chat-starter-hint",text:a.hint}):null,a.label]);return i.addEventListener("click",function(){n(a)}),i})))),t}function de(e){var n=function(){try{var r=l("textarea",{readonly:!0});r.value=e,r.style.position="fixed",r.style.opacity="0",document.body.appendChild(r),r.select();var t=document.execCommand("copy");return r.remove(),t}catch{return!1}};try{if(navigator.clipboard&&navigator.clipboard.writeText)return navigator.clipboard.writeText(e).then(function(){return!0},n)}catch{}return Promise.resolve(n())}function Q(e,n,r){var t=l("button",{type:"button",className:"ui-btn ui-btn--ghost gb-chat-copy",text:n}),a=0;return t.addEventListener("click",function(){de(e).then(function(i){i&&(t.textContent=r,clearTimeout(a),a=setTimeout(function(){t.textContent=n},1500))})}),t}function X(e,n,r){var t=Object.assign({},b,r||{}),a=(n||[]).filter(function(i){return i!=null&&i!==!1&&i!==""});return!e&&!a.length?null:l("div",{className:"gb-chat-meta"},[e?Q(e,t.copy,t.copied):null].concat(a.map(function(i){return l("span",null,i)})))}function ee(e,n,r){var t=Object.assign({},b,r||{});return n?l("div",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},[l("div",null,n.text),n.command?l("code",{className:"gb-chat-cmd",text:n.command}):null,n.command?l("div",null,Q(n.command,t.copyCommand,t.copied)):null,l("details",{className:"gb-chat-original"},[l("summary",{text:t.originalError}),l("span",null,e)])]):l("p",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},e)}function _(e){return l("p",{className:"gb-chat-note"},e)}var fe={warn:"gb-engine-notice gb-engine-notice--warn",down:"gb-engine-notice gb-engine-notice--down"},he={warn:"gb-engine-dot gb-engine-dot--warn",down:"gb-engine-dot gb-engine-dot--down"};function te(e,n,r,t){if(N.bridgeNotice)return N.bridgeNotice({text:k(e)?void 0:e,node:k(e)?e:void 0,tone:n,action:r,onOpenSetup:t});var a=n==="warn"?"warn":"down",i=l("p",{className:fe[a],role:"status"},[l("span",{className:he[a],"aria-hidden":"true"}),l("span",{className:"gb-engine-notice-text"},e)]);if(t){var s=l("button",{type:"button",className:"gb-engine-notice-link",text:r||"Open setup"});s.addEventListener("click",t),i.appendChild(s)}return i}function B(e,n){return l("div",{className:"gemboss-ec-bubble"},[e,n?l("span",{className:"gemboss-ec-cursor","aria-hidden":"true"}):null])}function ne(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});if(e.role==="note")return e.tone==="warn"||e.tone==="down"?te(e.text,e.tone,e.action&&e.action.label,e.action&&e.action.onClick):_(e.text);if(e.role==="user")return l("div",{className:"gemboss-ec-msg user",lang:e.lang||null},B(e.text));var t=!!e.text;return l("div",{className:"gemboss-ec-msg assistant",lang:e.lang||null},[e.steps&&e.steps.length?Z(e.steps,r):null,t?B(e.markdown===!1?e.text:G(e.text,n,r),e.pending):e.pending?J({verbs:n.pendingVerbs,since:n.since},r):null,n.renderExtra?n.renderExtra(e):null,e.warn?l("p",{className:"ui-notice ui-notice--warning gb-chat-notice"},e.warn):null,e.error?e.bridge?te(e.error,"down",null,n.onOpenSetup):ee(e.error,e.fix,r):null,e.stopped?_(r.stopped):null,!e.pending&&t?X(e.text,e.meta,r):null])}function ge(e){try{return JSON.stringify(e,function(n,r){return typeof r=="function"?"fn":k(r)?"node:"+(r.textContent||""):r})}catch{return String(Math.random())}}function pe(e,n){var r=e.querySelectorAll("details"),t=n.querySelectorAll("details"),a;for(a=0;a<r.length&&a<t.length;a++)r[a].open&&(t[a].open=!0);for(r=e.querySelectorAll(".gemboss-md-codemore"),t=n.querySelectorAll(".gemboss-md-codemore"),a=0;a<r.length&&a<t.length;a++)r[a].getAttribute("aria-expanded")==="true"&&t[a].click()}function me(e,n,r){r=r||{};var t=Object.assign({},b,r.labels||{}),a=e.__gbChat||(e.__gbChat={els:new Map,since:new Map}),i=[],s=new Map;if(!n.length&&(r.intro||r.starters&&r.starters.length)){var o=function(d){r.onStarter&&r.onStarter(d)};i.push(l("div",{className:"gemboss-ec-msg assistant"},[r.intro?B(r.intro):null,r.starters&&r.starters.length?I(r.starters,o,{title:r.startersTitle}):null,r.moreStarters&&r.moreStarters.length?I(r.moreStarters,o,{title:r.moreStartersTitle}):null]))}return n.forEach(function(d,f){var c=d.id!==void 0&&d.id!==null?"id:"+d.id:"at:"+f,p=ge(d),h=a.els.get(c);d.pending&&!d.text&&!a.since.has(c)&&a.since.set(c,Date.now());var g;h&&h.look===p?g=h.el:(g=ne(d,Object.assign({},r,{labels:t,since:a.since.get(c)})),h&&pe(h.el,g)),s.set(c,{el:g,look:p}),i.push(g)}),a.els=s,a.since.forEach(function(d,f){s.has(f)||a.since.delete(f)}),e.replaceChildren.apply(e,i),e}function ve(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});e.classList.add("gemboss-ec-scroll"),e.setAttribute("role","log"),e.hasAttribute("aria-label")||e.setAttribute("aria-label",n.label||r.thread);var t=!0,a=0,i=0,s=0,o=!1,d=null,f=function(){e.scrollTop=e.scrollHeight,d=e.scrollTop},c=function(){!t||a||(a=requestAnimationFrame(function(){a=0,t&&f()}))},p=function(){i=Date.now(),o=!0,t=!1},h=function(){o=!1},g=function(){var u=e.scrollHeight-e.clientHeight-e.scrollTop,w=d!==null&&e.scrollTop===d;d=null,u>24&&!w?t=!1:u<=4&&(!o||Date.now()-i>400)&&(t=!0)},m=function(u){u.deltaY<0?p():u.deltaY>0&&h()},v=function(u){s=u.touches[0]?u.touches[0].clientY:0},y=function(u){var w=u.touches[0]?u.touches[0].clientY:0;w>s+2?p():w<s-2&&h()},L=function(u){u.key==="ArrowUp"||u.key==="PageUp"||u.key==="Home"?p():(u.key==="ArrowDown"||u.key==="PageDown"||u.key==="End")&&h()};e.addEventListener("scroll",g,{passive:!0}),e.addEventListener("wheel",m,{passive:!0}),e.addEventListener("touchstart",v,{passive:!0}),e.addEventListener("touchmove",y,{passive:!0}),e.addEventListener("keydown",L),e.addEventListener("load",c,!0);var E=new MutationObserver(c);E.observe(e,{childList:!0,subtree:!0,characterData:!0});var S=typeof ResizeObserver>"u"?null:new ResizeObserver(c);return S&&S.observe(e),f(),{el:e,setBusy:function(u){u?e.setAttribute("aria-busy","true"):e.removeAttribute("aria-busy")},toEnd:function(){t=!0,f()},destroy:function(){cancelAnimationFrame(a),e.removeEventListener("scroll",g),e.removeEventListener("wheel",m),e.removeEventListener("touchstart",v),e.removeEventListener("touchmove",y),e.removeEventListener("keydown",L),e.removeEventListener("load",c,!0),E.disconnect(),S&&S.disconnect()}}}function be(){return M("0 0 24 24",[{d:"M21.4 11.1l-8.5 8.5a5.5 5.5 0 01-7.8-7.8l8.5-8.5a3.7 3.7 0 015.2 5.2l-8.5 8.5a1.8 1.8 0 01-2.6-2.6l7.8-7.8",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"}])}function ye(e){e=e||{};var n=e.label||b.attach,r=l("input",{type:"file",hidden:!0,accept:e.accept||null,multiple:e.multiple||null,tabindex:"-1"}),t=l("button",{type:"button",className:"gemboss-ec-attach-btn","aria-label":n,title:e.title||n,disabled:e.disabled||null},be());t.addEventListener("click",function(){r.click()}),r.addEventListener("change",function(){var i=Array.prototype.slice.call(r.files||[]);r.value="",i.length&&e.onFiles&&e.onFiles(i)});var a=document.createDocumentFragment();return a.appendChild(t),a.appendChild(r),a}var W=["full","compact","icon","tight"];function we(e){var n=e.querySelector(".gemboss-ec-toolbar-lead"),r=e.querySelector(".gemboss-ec-toolbar");if(n&&n.scrollWidth>n.clientWidth+1||r&&r.scrollWidth>r.clientWidth+1)return!0;var t,a=e.querySelectorAll(".gemboss-ec-chiplabel, .gb-engine-chiplabel");for(t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].scrollWidth>a[t].clientWidth+1)return!0;for(a=e.querySelectorAll(".gemboss-ec-chipctl, .gb-engine-chip"),t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].tagName!=="SELECT"&&a[t].scrollWidth>a[t].clientWidth+1)return!0;return!1}function Ne(e,n){e&&!k(e)&&(n=e,e=null),n=n||{};var r=Object.assign({},b,n.labels||{}),t=!1,a=l("textarea",{rows:n.rows||1,maxlength:n.maxLength||null,"aria-label":n.label||r.message,placeholder:n.placeholder||null}),i=l("div",{className:"gemboss-ec-toolbar-lead"},n.leading||null),s=n.tools?l("div",{className:"gemboss-ec-toolbar-tools"},n.tools):null,o=l("button",{type:"submit",disabled:!0,"aria-label":r.send,title:r.send},M("0 0 20 20",[{d:"M10.53 4.47a.75.75 0 0 0-1.06 0l-4.5 4.5a.75.75 0 0 0 1.06 1.06l3.22-3.22v8.69a.75.75 0 0 0 1.5 0v-8.69l3.22 3.22a.75.75 0 1 0 1.06-1.06l-4.5-4.5Z"}])),d=l("button",{type:"button",className:"gemboss-ec-stop","aria-label":r.stop,title:r.stop},M("0 0 12 12",[{tag:"rect",x:"1",y:"1",width:"10",height:"10",rx:"2"}])),f=l("div",{className:"gemboss-ec-toolbar"},[i,s,n.status||null,o]),c=l("div",{className:"gemboss-ec-input","data-density":"full"},[n.top||null,a,f]);e&&e.appendChild(c);var p=function(){return!a.disabled&&a.value.trim()!==""&&(!t||n.sendWhileBusy||!n.onStop)},h=function(){var u=t&&!!n.onStop,w=u?d:o,re=u?o:d;re.parentNode&&re.replaceWith(w),o.disabled=!p()},g=function(){p()&&(n.onSend&&n.onSend(a.value),n.thread&&n.thread.toEnd&&n.thread.toEnd(),n.keepValue||(a.value="",m(),h()))},m=function(){if(!(a.offsetParent===null||a.scrollHeight===0)){var u=parseFloat(getComputedStyle(a).maxHeight)||84;a.style.height="auto";var w=a.scrollHeight+(a.offsetHeight-a.clientHeight);a.style.height=Math.min(w,u)+"px",a.style.overflowY=w>u?"auto":"hidden"}};a.addEventListener("input",function(){m(),h(),n.onChange&&n.onChange(a.value)}),a.addEventListener("keydown",function(u){n.onKeyDown&&n.onKeyDown(u),!u.defaultPrevented&&u.key==="Enter"&&!u.shiftKey&&!u.isComposing&&u.keyCode!==229&&(u.preventDefault(),g())}),o.addEventListener("click",function(u){u.preventDefault(),g()}),d.addEventListener("click",function(){n.onStop&&n.onStop(),a.focus()});var v=!1,y=function(){if(!(v||c.offsetParent===null)){v=!0;for(var u=0;u<W.length&&(c.setAttribute("data-density",W[u]),!(u===W.length-1||!we(c)));u++);v=!1,n.onDensity&&n.onDensity(c.getAttribute("data-density"))}},L=-1,E=typeof ResizeObserver>"u"?null:new ResizeObserver(function(){c.clientWidth!==L&&(L=c.clientWidth,y(),m())});E&&E.observe(c);var S=new MutationObserver(function(){v||y()});return S.observe(i,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-label","title","aria-label"]}),document.fonts&&document.fonts.ready&&document.fonts.ready.then(y),requestAnimationFrame(function(){y(),m()}),{el:c,textarea:a,value:function(){return a.value},setValue:function(u){a.value=u==null?"":String(u),m(),h()},setBusy:function(u){t=!!u,h()},setDisabled:function(u){a.disabled=!!u,h()},focus:function(){a.focus()},measure:y,destroy:function(){E&&E.disconnect(),S.disconnect(),c.remove()}}}var xe="The bridge on your machine is not responding: it was stopped, or the machine just slept. The panel is checking again; rerun the install command if it does not come back.",Ee="The bridge on your machine rejected this browser's pairing code (it was installed with another code). Pair again in Setup.";function Se(e,n){return e&&typeof e=="object"&&(e.atBridge=!0,n!==void 0&&(e.status=n)),e}function Ae(e,n){return n=n||{},e&&e.name==="AbortError"?{error:null,bridge:!1,stopped:!0}:e&&e.atBridge&&e.status===401?{error:n.rejected||Ee,bridge:!0,stopped:!1}:e&&e.atBridge&&(e instanceof TypeError||e.name==="TimeoutError")?{error:n.gone||xe,bridge:!0,stopped:!1}:{error:e&&e.message||String(e),bridge:!1,stopped:!1}}N.engineMark||(N.engineMark=Y),N.chat={markdown:G,parseBlocks:K,parseInline:F,linkTarget:C,imageSource:H,message:ne,render:me,thread:ve,steps:Z,pending:J,starters:I,meta:X,error:ee,note:_,composer:Ne,attach:ye,labels:b,atBridge:Se,turnError:Ae}})(typeof globalThis<"u"?globalThis:this);
|
|
6
|
+
`),o.textContent=a?n.showLess:n.showAll.replace("{n}",String(r.length)),o.setAttribute("aria-expanded",String(a))}),s.appendChild(o)}return s}var ue={1:"h3",2:"h4",3:"h5"};function G(e,n,r){n=n||{};var t=Object.assign({},b,r||n.labels||{}),a=document.createDocumentFragment();return K(e).forEach(function(i){var s;i.t==="heading"?s=l(ue[i.level],{className:"gemboss-md-h gemboss-md-h"+i.level},x(i.text,n,t)):i.t==="code"?s=oe(i,t):i.t==="table"?s=l("div",{className:"gemboss-md-tablewrap"},l("table",{className:"gemboss-md-table"},[l("thead",null,l("tr",null,i.head.map(function(o){return l("th",null,x(o,n,t))}))),l("tbody",null,i.rows.map(function(o){return l("tr",null,i.head.map(function(d,f){return l("td",null,x(o[f]||"",n,t))}))}))])):i.t==="list"?s=l(i.ordered?"ol":"ul",{className:"gemboss-md-list",start:i.ordered&&i.start!==void 0?i.start:null},i.items.map(function(o){return l("li",null,x(o,n,t))})):i.t==="quote"?s=l("blockquote",{className:"gemboss-md-quote"},z(i.lines,n,t)):i.t==="rule"?s=l("hr",{className:"gemboss-md-rule"}):s=l("p",{className:"gemboss-md-p"},z(i.lines,n,t)),a.appendChild(s)}),a}function ce(){for(var e=12,n=1.7,r=.5,t=9.6,a=[],i=function(g){return Math.round(g*100)/100},s=function(g,m){return i(g)+" "+i(m)},o=0;o<8;o++){var d=o*Math.PI/4,f=Math.cos(d),c=Math.sin(d),p=-c,h=f;a.push(["M"+s(e+p*n,e+h*n),"L"+s(e+f*t+p*r,e+c*t+h*r),"Q"+s(e+f*(t+.6),e+c*(t+.6))+" "+s(e+f*t-p*r,e+c*t-h*r),"L"+s(e-p*n,e-h*n),"Z"].join(""))}return a.join(" ")}var V={claude:{path:ce(),label:"Claude Code"},codex:{path:"M12 2.6 20.1 7.3V16.7L12 21.4 3.9 16.7V7.3ZM12 6.9 7.6 9.45v5.1L12 17.1l4.4-2.55v-5.1Z",label:"Codex"}};function Y(e,n){var r=Object.prototype.hasOwnProperty.call(V,e)?V[e]:null;if(!r)return null;n=n||{};var t=document.createElementNS(A,"svg");if(t.setAttribute("class",n.className||"gemboss-engine-mark"),t.setAttribute("viewBox","0 0 24 24"),t.setAttribute("fill","currentColor"),n.decorative)t.setAttribute("aria-hidden","true"),t.setAttribute("focusable","false");else{t.setAttribute("role","img"),t.setAttribute("aria-label",n.title||r.label);var a=document.createElementNS(A,"title");a.textContent=n.title||r.label,t.appendChild(a)}var i=document.createElementNS(A,"path");return i.setAttribute("d",r.path),i.setAttribute("fill-rule","nonzero"),t.appendChild(i),t}function Z(e,n){var r=Object.assign({},b,n||{});return!e||!e.length?null:l("ul",{className:"gemboss-narr","aria-label":r.steps},e.map(function(t){var a=t.state||"ok",i;if(t.kind==="thinking")return i=[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u2733"}),a==="running"||!t.detail?l("span",{className:"gemboss-narr-text gemboss-narr-thinking-live"},t.label||r.thinking):l("details",{className:"gemboss-narr-thinking"},[l("summary",null,t.label||r.thought),l("div",{className:"gemboss-narr-thinking-body",text:t.detail})])],l("li",{className:"gemboss-narr-row thinking "+a},i);if(t.kind==="steer")return l("li",{className:"gemboss-narr-row steer "+a},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:"\u276F"}),l("span",{className:"gemboss-narr-text gemboss-narr-steer"},t.label)]);if(t.kind==="trace")return l("li",{className:"gemboss-narr-row trace "+a,title:t.error||null},[l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="failed"?"\u2715":"\u23FA"}),t.detail?l("details",{className:"gemboss-narr-trace"},[l("summary",null,l("code",null,t.label)),l("div",{className:"gemboss-narr-trace-body"},l("code",{text:t.detail}))]):l("code",{className:"gemboss-narr-text gemboss-narr-trace-call"},t.label)]);var s=t.engine?Y(t.engine):null;return l("li",{className:"gemboss-narr-row "+a,title:t.error||null},[s?l("span",{className:"gemboss-narr-dot gemboss-narr-dot-engine"},s):l("span",{className:"gemboss-narr-dot","aria-hidden":"true",text:a==="ok"?"\u2713":a==="failed"?"\u2715":"\xB7"}),l("span",{className:"gemboss-narr-text"},[t.label,t.target?l("span",{className:"gb-chat-target"},[" ",t.target]):null])])}))}function J(e,n){e=e||{};var r=Object.assign({},b,n||{}),t=e.verbs&&e.verbs.length?e.verbs:[r.working],a=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[0]+"\u2026"}),i=l("span",{className:"gemboss-ec-pending-meta","aria-hidden":"true",text:"0s"}),s=l("div",{className:"gemboss-ec-pending",role:"status"},[l("span",{className:"gemboss-assistant-sronly",text:e.label||t[0]}),l("span",{className:"gemboss-ec-pending-star","aria-hidden":"true",text:"\u2733"}),e.compact?null:a,i]),o=e.since||Date.now(),d=!1,f=-1,c=function(){var h=Math.max(0,Math.floor((Date.now()-o)/1e3));i.textContent=(e.tokens>0?e.tokens+" token"+(e.tokens===1?"":"s")+" \xB7 ":"")+h+"s";var g=t.length>1?Math.floor(h/2)%t.length:0;if(g!==f&&!e.compact){f=g;var m=l("span",{className:"gemboss-ec-pending-verb","aria-hidden":"true",text:t[f]+"\u2026"});a.replaceWith(m),a=m}};c();var p=setInterval(function(){if(s.isConnected)d=!0;else if(d||Date.now()-o>6e5){clearInterval(p);return}c()},250);return s}function I(e,n,r){r=r||{};var t=document.createDocumentFragment();return!e||!e.length||(r.title&&t.appendChild(l("p",{className:"gb-chat-label"},r.title)),t.appendChild(l("div",{className:"gemboss-ec-starters"},e.map(function(a){var i=l("button",{type:"button",className:"gemboss-ec-starter gb-chat-starter"},[a.hint?l("span",{className:"gb-chat-starter-hint",text:a.hint}):null,a.label]);return i.addEventListener("click",function(){n(a)}),i})))),t}function de(e){var n=function(){try{var r=l("textarea",{readonly:!0});r.value=e,r.style.position="fixed",r.style.opacity="0",document.body.appendChild(r),r.select();var t=document.execCommand("copy");return r.remove(),t}catch{return!1}};try{if(navigator.clipboard&&navigator.clipboard.writeText)return navigator.clipboard.writeText(e).then(function(){return!0},n)}catch{}return Promise.resolve(n())}function Q(e,n,r){var t=l("button",{type:"button",className:"ui-btn ui-btn--ghost gb-chat-copy",text:n}),a=0;return t.addEventListener("click",function(){de(e).then(function(i){i&&(t.textContent=r,clearTimeout(a),a=setTimeout(function(){t.textContent=n},1500))})}),t}function X(e,n,r){var t=Object.assign({},b,r||{}),a=(n||[]).filter(function(i){return i!=null&&i!==!1&&i!==""});return!e&&!a.length?null:l("div",{className:"gb-chat-meta"},[e?Q(e,t.copy,t.copied):null].concat(a.map(function(i){return l("span",null,i)})))}function ee(e,n,r){var t=Object.assign({},b,r||{});return n?l("div",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},[l("div",null,n.text),n.command?l("code",{className:"gb-chat-cmd",text:n.command}):null,n.command?l("div",null,Q(n.command,t.copyCommand,t.copied)):null,l("details",{className:"gb-chat-original"},[l("summary",{text:t.originalError}),l("span",null,e)])]):l("p",{className:"ui-notice ui-notice--danger gb-chat-error",role:"alert"},e)}function _(e){return l("p",{className:"gb-chat-note"},e)}var fe={warn:"gb-engine-notice gb-engine-notice--warn",down:"gb-engine-notice gb-engine-notice--down"},he={warn:"gb-engine-dot gb-engine-dot--warn",down:"gb-engine-dot gb-engine-dot--down"};function te(e,n,r,t){if(w.bridgeNotice)return w.bridgeNotice({text:k(e)?void 0:e,node:k(e)?e:void 0,tone:n,action:r,onOpenSetup:t});var a=n==="warn"?"warn":"down",i=l("p",{className:fe[a],role:"status"},[l("span",{className:he[a],"aria-hidden":"true"}),l("span",{className:"gb-engine-notice-text"},e)]);if(t){var s=l("button",{type:"button",className:"gb-engine-notice-link",text:r||"Open setup"});s.addEventListener("click",t),i.appendChild(s)}return i}function B(e,n){return l("div",{className:"gemboss-ec-bubble"},[e,n?l("span",{className:"gemboss-ec-cursor","aria-hidden":"true"}):null])}function ne(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});if(e.role==="note")return e.tone==="warn"||e.tone==="down"?te(e.text,e.tone,e.action&&e.action.label,e.action&&e.action.onClick):_(e.text);if(e.role==="user")return l("div",{className:"gemboss-ec-msg user",lang:e.lang||null},B(e.text));var t=!!e.text;return l("div",{className:"gemboss-ec-msg assistant",lang:e.lang||null},[e.steps&&e.steps.length?Z(e.steps,r):null,t?B(e.markdown===!1?e.text:G(e.text,n,r),e.pending):e.pending?J({verbs:n.pendingVerbs,since:n.since},r):null,n.renderExtra?n.renderExtra(e):null,e.warn?l("p",{className:"ui-notice ui-notice--warning gb-chat-notice"},e.warn):null,e.error?e.bridge?te(e.error,"down",null,n.onOpenSetup):ee(e.error,e.fix,r):null,e.stopped?_(r.stopped):null,!e.pending&&t?X(e.text,e.meta,r):null])}function ge(e){try{return JSON.stringify(e,function(n,r){return typeof r=="function"?"fn":k(r)?"node:"+(r.textContent||""):r})}catch{return String(Math.random())}}function pe(e,n){var r=e.querySelectorAll("details"),t=n.querySelectorAll("details"),a;for(a=0;a<r.length&&a<t.length;a++)r[a].open&&(t[a].open=!0);for(r=e.querySelectorAll(".gemboss-md-codemore"),t=n.querySelectorAll(".gemboss-md-codemore"),a=0;a<r.length&&a<t.length;a++)r[a].getAttribute("aria-expanded")==="true"&&t[a].click()}function me(e,n,r){r=r||{};var t=Object.assign({},b,r.labels||{}),a=e.__gbChat||(e.__gbChat={els:new Map,since:new Map}),i=[],s=new Map;if(!n.length&&(r.intro||r.starters&&r.starters.length)){var o=function(d){r.onStarter&&r.onStarter(d)};i.push(l("div",{className:"gemboss-ec-msg assistant"},[r.intro?B(r.intro):null,r.starters&&r.starters.length?I(r.starters,o,{title:r.startersTitle}):null,r.moreStarters&&r.moreStarters.length?I(r.moreStarters,o,{title:r.moreStartersTitle}):null]))}return n.forEach(function(d,f){var c=d.id!==void 0&&d.id!==null?"id:"+d.id:"at:"+f,p=ge(d),h=a.els.get(c);d.pending&&!d.text&&!a.since.has(c)&&a.since.set(c,Date.now());var g;h&&h.look===p?g=h.el:(g=ne(d,Object.assign({},r,{labels:t,since:a.since.get(c)})),h&&pe(h.el,g)),s.set(c,{el:g,look:p}),i.push(g)}),a.els=s,a.since.forEach(function(d,f){s.has(f)||a.since.delete(f)}),e.replaceChildren.apply(e,i),e}function ve(e,n){n=n||{};var r=Object.assign({},b,n.labels||{});e.classList.add("gemboss-ec-scroll"),e.setAttribute("role","log"),e.hasAttribute("aria-label")||e.setAttribute("aria-label",n.label||r.thread);var t=!0,a=0,i=0,s=0,o=!1,d=null,f=function(){e.scrollTop=e.scrollHeight,d=e.scrollTop},c=function(){!t||a||(a=requestAnimationFrame(function(){a=0,t&&f()}))},p=function(){i=Date.now(),o=!0,t=!1},h=function(){o=!1},g=function(){var u=e.scrollHeight-e.clientHeight-e.scrollTop,N=d!==null&&e.scrollTop===d;d=null,u>24&&!N?t=!1:u<=4&&(!o||Date.now()-i>400)&&(t=!0)},m=function(u){u.deltaY<0?p():u.deltaY>0&&h()},v=function(u){s=u.touches[0]?u.touches[0].clientY:0},y=function(u){var N=u.touches[0]?u.touches[0].clientY:0;N>s+2?p():N<s-2&&h()},L=function(u){u.key==="ArrowUp"||u.key==="PageUp"||u.key==="Home"?p():(u.key==="ArrowDown"||u.key==="PageDown"||u.key==="End")&&h()};e.addEventListener("scroll",g,{passive:!0}),e.addEventListener("wheel",m,{passive:!0}),e.addEventListener("touchstart",v,{passive:!0}),e.addEventListener("touchmove",y,{passive:!0}),e.addEventListener("keydown",L),e.addEventListener("load",c,!0);var E=new MutationObserver(c);E.observe(e,{childList:!0,subtree:!0,characterData:!0});var S=typeof ResizeObserver>"u"?null:new ResizeObserver(c);return S&&S.observe(e),f(),{el:e,setBusy:function(u){u?e.setAttribute("aria-busy","true"):e.removeAttribute("aria-busy")},toEnd:function(){t=!0,f()},destroy:function(){cancelAnimationFrame(a),e.removeEventListener("scroll",g),e.removeEventListener("wheel",m),e.removeEventListener("touchstart",v),e.removeEventListener("touchmove",y),e.removeEventListener("keydown",L),e.removeEventListener("load",c,!0),E.disconnect(),S&&S.disconnect()}}}function be(){return M("0 0 24 24",[{d:"M21.4 11.1l-8.5 8.5a5.5 5.5 0 01-7.8-7.8l8.5-8.5a3.7 3.7 0 015.2 5.2l-8.5 8.5a1.8 1.8 0 01-2.6-2.6l7.8-7.8",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"}])}function ye(e){e=e||{};var n=e.label||b.attach,r=l("input",{type:"file",hidden:!0,accept:e.accept||null,multiple:e.multiple||null,tabindex:"-1"}),t=l("button",{type:"button",className:"gemboss-ec-attach-btn","aria-label":n,title:e.title||n,disabled:e.disabled||null},be());t.addEventListener("click",function(){r.click()}),r.addEventListener("change",function(){var i=Array.prototype.slice.call(r.files||[]);r.value="",i.length&&e.onFiles&&e.onFiles(i)});var a=document.createDocumentFragment();return a.appendChild(t),a.appendChild(r),a}var W=["full","compact","icon","tight"];function Ne(e){var n=e.querySelector(".gemboss-ec-toolbar-lead"),r=e.querySelector(".gemboss-ec-toolbar");if(n&&n.scrollWidth>n.clientWidth+1||r&&r.scrollWidth>r.clientWidth+1)return!0;var t,a=e.querySelectorAll(".gemboss-ec-chiplabel, .gb-engine-chiplabel");for(t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].scrollWidth>a[t].clientWidth+1)return!0;for(a=e.querySelectorAll(".gemboss-ec-chipctl, .gb-engine-chip"),t=0;t<a.length;t++)if(a[t].offsetParent!==null&&a[t].tagName!=="SELECT"&&a[t].scrollWidth>a[t].clientWidth+1)return!0;return!1}function we(e,n){e&&!k(e)&&(n=e,e=null),n=n||{};var r=Object.assign({},b,n.labels||{}),t=!1,a=l("textarea",{rows:n.rows||1,maxlength:n.maxLength||null,"aria-label":n.label||r.message,placeholder:n.placeholder||null}),i=l("div",{className:"gemboss-ec-toolbar-lead"},n.leading||null),s=n.tools?l("div",{className:"gemboss-ec-toolbar-tools"},n.tools):null,o=l("button",{type:"submit",disabled:!0,"aria-label":r.send,title:r.send},M("0 0 20 20",[{d:"M10.53 4.47a.75.75 0 0 0-1.06 0l-4.5 4.5a.75.75 0 0 0 1.06 1.06l3.22-3.22v8.69a.75.75 0 0 0 1.5 0v-8.69l3.22 3.22a.75.75 0 1 0 1.06-1.06l-4.5-4.5Z"}])),d=l("button",{type:"button",className:"gemboss-ec-stop","aria-label":r.stop,title:r.stop},M("0 0 12 12",[{tag:"rect",x:"1",y:"1",width:"10",height:"10",rx:"2"}])),f=l("div",{className:"gemboss-ec-toolbar"},[i,s,n.status||null,o]),c=l("div",{className:"gemboss-ec-input","data-density":"full"},[n.top||null,a,f]);e&&e.appendChild(c);var p=function(){return!a.disabled&&a.value.trim()!==""&&(!t||n.sendWhileBusy||!n.onStop)},h=function(){var u=t&&!!n.onStop,N=u?d:o,re=u?o:d;re.parentNode&&re.replaceWith(N),o.disabled=!p()},g=function(){p()&&(n.onSend&&n.onSend(a.value),n.thread&&n.thread.toEnd&&n.thread.toEnd(),n.keepValue||(a.value="",m(),h()))},m=function(){if(!(a.offsetParent===null||a.scrollHeight===0)){var u=parseFloat(getComputedStyle(a).maxHeight)||84;a.style.height="auto";var N=a.scrollHeight+(a.offsetHeight-a.clientHeight);a.style.height=Math.min(N,u)+"px",a.style.overflowY=N>u?"auto":"hidden"}};a.addEventListener("input",function(){m(),h(),n.onChange&&n.onChange(a.value)}),a.addEventListener("keydown",function(u){n.onKeyDown&&n.onKeyDown(u),!u.defaultPrevented&&u.key==="Enter"&&!u.shiftKey&&!u.isComposing&&u.keyCode!==229&&(u.preventDefault(),g())}),o.addEventListener("click",function(u){u.preventDefault(),g()}),d.addEventListener("click",function(){n.onStop&&n.onStop(),a.focus()});var v=!1,y=function(){if(!(v||c.offsetParent===null)){v=!0;for(var u=0;u<W.length&&(c.setAttribute("data-density",W[u]),!(u===W.length-1||!Ne(c)));u++);v=!1,n.onDensity&&n.onDensity(c.getAttribute("data-density"))}},L=-1,E=typeof ResizeObserver>"u"?null:new ResizeObserver(function(){c.clientWidth!==L&&(L=c.clientWidth,y(),m())});E&&E.observe(c);var S=new MutationObserver(function(){v||y()});return S.observe(i,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-label","title","aria-label"]}),document.fonts&&document.fonts.ready&&document.fonts.ready.then(y),requestAnimationFrame(function(){y(),m()}),{el:c,textarea:a,value:function(){return a.value},setValue:function(u){a.value=u==null?"":String(u),m(),h()},setBusy:function(u){t=!!u,h()},setDisabled:function(u){a.disabled=!!u,h()},focus:function(){a.focus()},measure:y,destroy:function(){E&&E.disconnect(),S.disconnect(),c.remove()}}}var xe="The bridge on your machine is not responding: it was stopped, or the machine just slept. The panel is checking again; rerun the install command if it does not come back.",Ee="The bridge on your machine rejected this browser's pairing code (it was installed with another code). Pair again in Setup.";function Se(e,n){return e&&typeof e=="object"&&(e.atBridge=!0,n!==void 0&&(e.status=n)),e}function Ae(e,n){return n=n||{},e&&e.name==="AbortError"?{error:null,bridge:!1,stopped:!0}:e&&e.atBridge&&e.status===401?{error:n.rejected||Ee,bridge:!0,stopped:!1}:e&&e.atBridge&&(e instanceof TypeError||e.name==="TimeoutError")?{error:n.gone||xe,bridge:!0,stopped:!1}:{error:e&&e.message||String(e),bridge:!1,stopped:!1}}w.engineMark||(w.engineMark=Y),w.chat={markdown:G,parseBlocks:K,parseInline:F,linkTarget:C,imageSource:H,message:ne,render:me,thread:ve,steps:Z,pending:J,starters:I,meta:X,error:ee,note:_,composer:we,attach:ye,labels:b,atBridge:Se,turnError:Ae}})(typeof globalThis<"u"?globalThis:this);
|
package/src/editor/controls.tsx
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
// · the edge box hands each keystroke to the caller (`onChange(side, raw)`) and says when to commit (`onCommit`, on
|
|
12
12
|
// blur): the caller decides what an empty edge means, and the admin has three answers to that on purpose.
|
|
13
13
|
import "../editor.css"; // its own stylesheet travels with it (0.23.0): a surface that never renders it never loads it
|
|
14
|
-
import { useEffect, useRef, useState } from "react";
|
|
14
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
15
15
|
import type React from "react";
|
|
16
16
|
import { cssUrl } from "./css-url";
|
|
17
|
-
import { swatchState } from "./swatch";
|
|
17
|
+
import { swatchState, swatchWhy } from "./swatch";
|
|
18
18
|
import { Segmented } from "../segmented";
|
|
19
19
|
|
|
20
20
|
const blurOnEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
@@ -104,12 +104,9 @@ export function ValueControl({ id, label, presets, text, placeholder, suffix, sl
|
|
|
104
104
|
{presets.length ? <Seg label={`${label} presets`} value={lit} disabled={disabled} onChange={onPreset}
|
|
105
105
|
options={presets.map((p) => ({ value: p.value, label: p.label }))} /> : null}
|
|
106
106
|
<div className="gemboss-ei-value-row">
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
onKeyDown={(e) => { onKeyDown?.(e); if (!e.defaultPrevented) blurOnEnter(e); }} />
|
|
111
|
-
{suffix ? <span className="gemboss-ei-suffix">{suffix}</span> : null}
|
|
112
|
-
</span>
|
|
107
|
+
<UnitField className="gemboss-ei-value-box" unit={suffix} id={id} inputMode={inputMode} value={text} placeholder={placeholder}
|
|
108
|
+
disabled={disabled} aria-label={label} onChange={(e) => onText(e.target.value)} onBlur={onCommit}
|
|
109
|
+
onKeyDown={(e) => { onKeyDown?.(e); if (!e.defaultPrevented) blurOnEnter(e); }} />
|
|
113
110
|
{slider && onSlide ? <RangeControl min={slider.min} max={slider.max} step={slider.step} value={slider.value} disabled={disabled} onChange={onSlide} /> : null}
|
|
114
111
|
</div>
|
|
115
112
|
{note ? <p className="gemboss-ei-hint" role="status">{note}</p> : null}
|
|
@@ -117,6 +114,33 @@ export function ValueControl({ id, label, presets, text, placeholder, suffix, sl
|
|
|
117
114
|
);
|
|
118
115
|
}
|
|
119
116
|
|
|
117
|
+
// ── UnitField ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export interface UnitFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "style"> {
|
|
120
|
+
/** The unit after the value, inside the box ("px", "ms", "%"). Empty draws none. */
|
|
121
|
+
unit?: string;
|
|
122
|
+
/** Classes on the box, e.g. `gemboss-ei-inp` for a row's full-width control, `gemboss-ei-value-box` in ValueControl. */
|
|
123
|
+
className?: string;
|
|
124
|
+
/** Style on the box (a fixed width where a row lays out several controls). */
|
|
125
|
+
style?: React.CSSProperties;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A number box with its unit INSIDE (0.40.0; control standard, research pickers-2026-10-16 §13b U1), as Polaris TextField's
|
|
130
|
+
* `suffix`: the kit's field group (`.gemboss-field--group`, the box) around a bare input and `.gemboss-field__suffix`. Drawn beside
|
|
131
|
+
* the box, the unit was a second edge: 71 rows of the element panel stopped short of the panel's right edge. The box is a <label>,
|
|
132
|
+
* so a click or a tap anywhere in it, the unit included, focuses the input (one 44px target under a finger), as the kit's other
|
|
133
|
+
* field groups do. One owner for the shape: FieldRenderer's number, ValueControl's box and the theme inspector's design rows.
|
|
134
|
+
*/
|
|
135
|
+
export function UnitField({ unit, className, style, ...input }: UnitFieldProps) {
|
|
136
|
+
return (
|
|
137
|
+
<label className={`gemboss-field gemboss-field--group${className ? ` ${className}` : ""}`} style={style}>
|
|
138
|
+
<input {...input} className="gemboss-field__bare" />
|
|
139
|
+
{unit ? <span className="gemboss-field__suffix">{unit}</span> : null}
|
|
140
|
+
</label>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
120
144
|
// ── ColorControl ────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
121
145
|
|
|
122
146
|
export interface ColorControlProps {
|
|
@@ -139,15 +163,25 @@ export interface ColorControlProps {
|
|
|
139
163
|
export function ColorControl({ id, value, onChange, disabled, label, placeholder }: ColorControlProps) {
|
|
140
164
|
const [draft, setDraft] = useState(value);
|
|
141
165
|
useEffect(() => { setDraft(value); }, [value]);
|
|
142
|
-
|
|
166
|
+
// NOT SET IS NOT BLACK (control standard §13b U7, Chris 20/10/2026): an empty colour drew the swatch black beside "Not set", so a
|
|
167
|
+
// seller read a colour nobody chose. Empty, the swatch is white with the slash a colour picker draws for "none" (editor.css
|
|
168
|
+
// `[data-empty]`), and opens on white.
|
|
169
|
+
const empty = !draft.trim();
|
|
170
|
+
const { swatch, usable } = swatchState(draft, "#ffffff");
|
|
171
|
+
// Why the swatch is off, said under it, read with it (a tooltip alone reached neither a finger nor the keys). The COMMITTED
|
|
172
|
+
// value's reason, and kept while the panel saves (it disables every field): taken from the draft or dropped while saving, the
|
|
173
|
+
// line came and went on every write and while a hex was typed (review of #4636). A draft that is a whole hex has none.
|
|
174
|
+
const why = usable ? null : swatchWhy(value);
|
|
175
|
+
const whyId = useId();
|
|
143
176
|
return (
|
|
144
177
|
<div className="gemboss-ei-color">
|
|
145
|
-
<input type="color" className="gemboss-ei-swatch" aria-label={`${label} swatch`} value={swatch}
|
|
178
|
+
<input type="color" className="gemboss-ei-swatch" aria-label={`${label} swatch`} value={swatch} data-empty={empty ? "" : undefined}
|
|
146
179
|
disabled={disabled || !usable}
|
|
147
|
-
title={usable ? undefined :
|
|
180
|
+
title={usable ? undefined : swatchWhy(draft) ?? undefined} aria-describedby={why ? whyId : undefined}
|
|
148
181
|
onChange={(e) => { setDraft(e.target.value); onChange(e.target.value); }} />
|
|
149
|
-
<input id={id} className="gemboss-field gemboss-ei-inp" placeholder={placeholder ?? "
|
|
182
|
+
<input id={id} className="gemboss-field gemboss-ei-inp" placeholder={placeholder ?? "Not set"} value={draft} disabled={disabled}
|
|
150
183
|
onChange={(e) => setDraft(e.target.value)} onBlur={() => onChange(draft)} onKeyDown={blurOnEnter} />
|
|
184
|
+
{why ? <p id={whyId} className="gemboss-ei-color-why">{why}</p> : null}
|
|
151
185
|
</div>
|
|
152
186
|
);
|
|
153
187
|
}
|
|
@@ -254,7 +288,9 @@ export function EdgeBox({ label, values, placeholders, onChange, onCommit, disab
|
|
|
254
288
|
|
|
255
289
|
// ── ImageControl ────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
256
290
|
|
|
257
|
-
/** A
|
|
291
|
+
/** A field's own button (an image slot's Library / Generate / Clear, a link's Choose, a panel's "+ Add"): the kit's `.ui-btn`, a Polaris
|
|
292
|
+
* secondary button (28px, 12px / 550, radius 8; 44px under a finger). It was a dashed pill (`.gemboss-ei-addchip`, 27px, 11px / 600,
|
|
293
|
+
* radius 999): control standard U3, research pickers-2026-10-16 §13b. */
|
|
258
294
|
export function ChipButton({ disabled, expanded, onClick, children }: {
|
|
259
295
|
disabled?: boolean;
|
|
260
296
|
/** `aria-expanded`, for a chip that opens something under it (the Library grid). */
|
|
@@ -262,7 +298,7 @@ export function ChipButton({ disabled, expanded, onClick, children }: {
|
|
|
262
298
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
263
299
|
children: React.ReactNode;
|
|
264
300
|
}) {
|
|
265
|
-
return <button type="button" className="
|
|
301
|
+
return <button type="button" className="ui-btn" disabled={disabled} aria-expanded={expanded} onClick={onClick}>{children}</button>;
|
|
266
302
|
}
|
|
267
303
|
|
|
268
304
|
export interface ImageControlProps {
|
package/src/editor/index.ts
CHANGED
|
@@ -36,10 +36,10 @@ export {
|
|
|
36
36
|
type FieldProps, type InspectorGroupProps, type PanelHeaderProps, type PanelTab, type PanelTabsProps,
|
|
37
37
|
} from "./panel";
|
|
38
38
|
export {
|
|
39
|
-
ChipButton, ColorControl, EdgeBox, ImageControl, RangeControl, Seg, ValueControl,
|
|
40
|
-
type ColorControlProps, type EdgeBoxProps, type EdgeSide, type ImageControlProps, type RangeControlProps, type SegOption, type SegProps, type ValueControlProps,
|
|
39
|
+
ChipButton, ColorControl, EdgeBox, ImageControl, RangeControl, Seg, UnitField, ValueControl,
|
|
40
|
+
type ColorControlProps, type EdgeBoxProps, type EdgeSide, type ImageControlProps, type RangeControlProps, type SegOption, type SegProps, type UnitFieldProps, type ValueControlProps,
|
|
41
41
|
} from "./controls";
|
|
42
|
-
export { swatchState, type SwatchState } from "./swatch";
|
|
42
|
+
export { swatchState, swatchWhy, type SwatchState } from "./swatch";
|
|
43
43
|
export { cssUrl } from "./css-url";
|
|
44
44
|
// Step 4 (0.22.0): the page's layers and the drawer (`layers`, stateless; the page editor draws them), a list whose rows
|
|
45
45
|
// reorder (`sortable`, "use client": a drag holds state; no admin screen uses it), and the element outline (`tree`, the
|
package/src/editor/panel.tsx
CHANGED
|
@@ -112,7 +112,7 @@ export function PanelAction({ label, title, disabled, onClick, children }: {
|
|
|
112
112
|
// ── InspectorGroup ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
113
113
|
|
|
114
114
|
export interface InspectorGroupProps {
|
|
115
|
-
/** The header:
|
|
115
|
+
/** The header: 13px / 650, sentence case (`.gemboss-ei-gh`). */
|
|
116
116
|
title?: React.ReactNode;
|
|
117
117
|
/** A short aside after the title, in the header's own line ("· all screens"). Collapsible groups only. */
|
|
118
118
|
note?: React.ReactNode;
|
|
@@ -129,15 +129,21 @@ export interface InspectorGroupProps {
|
|
|
129
129
|
collapsible?: boolean;
|
|
130
130
|
/** A muted line after the body (`p.gemboss-ei-hint`). */
|
|
131
131
|
hint?: React.ReactNode;
|
|
132
|
+
/**
|
|
133
|
+
* `data-*` attributes on the group itself (`.gemboss-ei-grp`, or the static `section`): what a page finds the group by, without a
|
|
134
|
+
* wrapper. A wrapper made every group the `:last-child` of its own parent, so the divider between groups was dropped (control
|
|
135
|
+
* standard §13b, 19/10/2026).
|
|
136
|
+
*/
|
|
137
|
+
attrs?: Record<`data-${string}`, string>;
|
|
132
138
|
children?: React.ReactNode;
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
export function InspectorGroup({ title, note, open, defaultOpen = true, onOpenChange, collapsible = true, hint, children }: InspectorGroupProps) {
|
|
141
|
+
export function InspectorGroup({ title, note, open, defaultOpen = true, onOpenChange, collapsible = true, hint, attrs, children }: InspectorGroupProps) {
|
|
136
142
|
const [own, setOwn] = useState(defaultOpen);
|
|
137
143
|
const hintNode = hint ? <p className="gemboss-ei-hint">{hint}</p> : null;
|
|
138
144
|
if (!collapsible) {
|
|
139
145
|
return (
|
|
140
|
-
<section className="gemboss-ei-group">
|
|
146
|
+
<section className="gemboss-ei-group" {...attrs}>
|
|
141
147
|
{title ? <h4 className="gemboss-ei-grouphead">{title}</h4> : null}
|
|
142
148
|
{children}
|
|
143
149
|
{hintNode}
|
|
@@ -146,7 +152,7 @@ export function InspectorGroup({ title, note, open, defaultOpen = true, onOpenCh
|
|
|
146
152
|
}
|
|
147
153
|
const isOpen = open ?? own;
|
|
148
154
|
return (
|
|
149
|
-
<div className="gemboss-ei-grp">
|
|
155
|
+
<div className="gemboss-ei-grp" {...attrs}>
|
|
150
156
|
<button type="button" className="gemboss-ei-gh" data-open={isOpen ? "open" : "closed"} aria-expanded={isOpen}
|
|
151
157
|
onClick={() => { if (open === undefined) setOwn(!isOpen); onOpenChange?.(!isOpen); }}>
|
|
152
158
|
<span className="gemboss-ei-chev" aria-hidden>{isOpen ? "▾" : "▸"}</span>{title}
|
package/src/editor/repeater.tsx
CHANGED
|
@@ -16,10 +16,20 @@
|
|
|
16
16
|
//
|
|
17
17
|
// Rows are kept by KEY (useKeyedRows), not by index: a row's open state and its controls' unsaved typing follow it when it
|
|
18
18
|
// moves, and a field writes to its row by key. The drag is useRowDrag (sortable.tsx), the same rule as the Layers list.
|
|
19
|
+
//
|
|
20
|
+
// ITEMS IN A POPOVER (item management IM1a, Chris 20/10/2026, GemPages 7.5's "Item management" read in its code): with
|
|
21
|
+
// `editIn="popover"` the list stays one line per row and the row picked shows its controls in a layer under the list (the
|
|
22
|
+
// browser's top layer, placed like the kit's menus, menu-layer.ts), one row at a time; the other rows stay in view and a click
|
|
23
|
+
// on one moves the layer to it. It closes on its ✕, on Escape (the focus goes back to the row) or on the row clicked again,
|
|
24
|
+
// never on a click elsewhere: a row's controls open pickers and dialogs of their own. The host hears the row picked
|
|
25
|
+
// (`onActive`), to show that item on the canvas. A row's picture (`thumb`, its first image) sits before its name.
|
|
26
|
+
// One row: its controls show in place, open, as the inline list draws it (nothing to choose between; a layer would cost a click).
|
|
27
|
+
// No popover API: the row picked opens in place, as the inline list does.
|
|
19
28
|
import "../editor.css"; // the field and the 44px rule under a finger (`.gemboss-ei-fld`)
|
|
20
29
|
import "../editor-repeater.css"; // its own part (16/10/2026): a surface that never draws a list never loads it
|
|
21
|
-
import { useEffect, useId, useRef, useState } from "react";
|
|
30
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
22
31
|
import type React from "react";
|
|
32
|
+
import { canFloatMenus, placeFloating } from "../menu-layer";
|
|
23
33
|
import { useRowDrag } from "./sortable";
|
|
24
34
|
|
|
25
35
|
let lastKey = 0;
|
|
@@ -139,9 +149,11 @@ export interface RepeaterListProps {
|
|
|
139
149
|
title: (index: number) => string;
|
|
140
150
|
/** A row's controls, drawn while it is open. */
|
|
141
151
|
body: (index: number) => React.ReactNode;
|
|
142
|
-
/** `to` is the index after the move.
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
/** `to` is the index after the move. Absent: the rows keep their order (no grip, no ↑ ↓, no Alt+arrows): a list whose order
|
|
153
|
+
* is kept elsewhere (items read from Content, in Content's order). */
|
|
154
|
+
onMove?: (from: number, to: number) => void;
|
|
155
|
+
/** Absent: no remove button (rows that are not this list's to remove). */
|
|
156
|
+
onRemove?: (index: number) => void;
|
|
145
157
|
/** Absent: no duplicate button (a list whose rows must differ). */
|
|
146
158
|
onDuplicate?: (index: number) => void;
|
|
147
159
|
/** Absent: no add button. */
|
|
@@ -158,6 +170,12 @@ export interface RepeaterListProps {
|
|
|
158
170
|
/** 44px buttons with a mouse too (`.gemboss-ei-rep--touch`): a surface whose every control is 44px (the page editor's
|
|
159
171
|
* content panel, check:page-revision-touchable). Under a finger they are 44px everywhere. */
|
|
160
172
|
touch?: boolean;
|
|
173
|
+
/** A row's picture before its name (its first image field), 20px; empty: none. */
|
|
174
|
+
thumb?: (index: number) => string | undefined;
|
|
175
|
+
/** Where a row's controls show: folded open under its header (default), or one row at a time in a layer under the list. */
|
|
176
|
+
editIn?: "inline" | "popover";
|
|
177
|
+
/** Popover mode: the row picked (null when none), so the host can show that item. */
|
|
178
|
+
onActive?: (index: number | null) => void;
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
// A function, not an element built at import: a page that compiles JSX against a global React (the admin's gates) sets that
|
|
@@ -165,8 +183,20 @@ export interface RepeaterListProps {
|
|
|
165
183
|
const DuplicateIcon = () => <svg viewBox="0 0 16 16" width="13" height="13" aria-hidden fill="none" stroke="currentColor" strokeWidth="1.4"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5" /><path d="M10.5 3.5v-.5A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v5A1.5 1.5 0 0 0 4 9.5h.5" /></svg>;
|
|
166
184
|
|
|
167
185
|
/** `.gemboss-ei-fld.gemboss-ei-rep` › label (+ count) › `.gemboss-ei-rep-list` › one `.gemboss-ei-rep-item` per row › add. */
|
|
168
|
-
export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove, onDuplicate, onAdd, addLabel = "+ Add item", canAdd = true, disabled, empty, rowAttrs, touch }: RepeaterListProps) {
|
|
186
|
+
export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove, onDuplicate, onAdd, addLabel = "+ Add item", canAdd = true, disabled, empty, rowAttrs, touch, thumb, editIn = "inline", onActive }: RepeaterListProps) {
|
|
169
187
|
const uid = useId();
|
|
188
|
+
// The layer only where there is a row to choose: a list of one shows it in place.
|
|
189
|
+
const popover = editIn === "popover" && keys.length > 1;
|
|
190
|
+
// Popover mode: the row picked, by key; the layer floats only where the browser has the popover API (read after the first
|
|
191
|
+
// paint, so the server's markup and the first client render agree), else the row opens in place.
|
|
192
|
+
const [active, setActive] = useState<string | null>(null);
|
|
193
|
+
const [canFloat, setCanFloat] = useState(false);
|
|
194
|
+
useEffect(() => { setCanFloat(canFloatMenus()); }, []);
|
|
195
|
+
const activeKey = active !== null && keys.includes(active) ? active : null;
|
|
196
|
+
const pick = (key: string | null) => { setActive(key); onActive?.(key === null ? null : keys.indexOf(key)); };
|
|
197
|
+
// A row removed while picked: its layer goes, and the host hears it.
|
|
198
|
+
useEffect(() => { if (active !== null && !keys.includes(active)) { setActive(null); onActive?.(null); } }, [keys, active, onActive]);
|
|
199
|
+
const popRef = useRef<HTMLDivElement>(null);
|
|
170
200
|
const [open, setOpen] = useState<ReadonlySet<string>>(() => new Set(keys.length <= 1 ? keys : []));
|
|
171
201
|
// A row that arrives after the list was drawn opens. Compared by content: a caller may hand a new array each render.
|
|
172
202
|
const [seen, setSeen] = useState<readonly string[]>(keys);
|
|
@@ -175,8 +205,10 @@ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove,
|
|
|
175
205
|
setSeen(keys);
|
|
176
206
|
if (fresh.length) setOpen((o) => new Set([...o, ...fresh]));
|
|
177
207
|
}
|
|
178
|
-
const toggle = (key: string) =>
|
|
179
|
-
|
|
208
|
+
const toggle = (key: string) => popover
|
|
209
|
+
? pick(activeKey === key ? null : key)
|
|
210
|
+
: setOpen((o) => { const next = new Set(o); if (!next.delete(key)) next.add(key); return next; });
|
|
211
|
+
const drag = useRowDrag(keys, onMove ?? (() => {}));
|
|
180
212
|
// A keyboard move keeps the focus on the moved row's name once it has landed (as SortableLayerList).
|
|
181
213
|
const refocus = useRef<{ key: string; from: number } | null>(null);
|
|
182
214
|
const listRef = useRef<HTMLDivElement>(null);
|
|
@@ -190,47 +222,85 @@ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove,
|
|
|
190
222
|
const row = [...(listRef.current?.children ?? [])].find((r) => (r as HTMLElement).dataset.key === want.key);
|
|
191
223
|
row?.querySelector<HTMLButtonElement>(".gemboss-ei-rep-toggle")?.focus();
|
|
192
224
|
}, [keys]);
|
|
225
|
+
// The layer under the list: in the top layer, as wide as the list, placed again when the panel or the page scrolls or the
|
|
226
|
+
// window resizes (a scroll inside the layer does not count), so it never floats away from its list.
|
|
227
|
+
useLayoutEffect(() => {
|
|
228
|
+
const el = popRef.current, list = listRef.current;
|
|
229
|
+
if (!el || !list || activeKey === null) return;
|
|
230
|
+
if (el.getAttribute("popover") !== "manual") el.setAttribute("popover", "manual");
|
|
231
|
+
if (!el.matches(":popover-open")) el.showPopover();
|
|
232
|
+
const place = () => { el.style.width = `${Math.round(list.getBoundingClientRect().width)}px`; placeFloating(el, list); };
|
|
233
|
+
place();
|
|
234
|
+
el.focus({ preventScroll: true });
|
|
235
|
+
const onMove = (e: Event) => { if (e.target instanceof Node && el.contains(e.target)) return; place(); };
|
|
236
|
+
window.addEventListener("scroll", onMove, true);
|
|
237
|
+
window.addEventListener("resize", onMove);
|
|
238
|
+
return () => {
|
|
239
|
+
window.removeEventListener("scroll", onMove, true);
|
|
240
|
+
window.removeEventListener("resize", onMove);
|
|
241
|
+
if (el.matches(":popover-open")) el.hidePopover();
|
|
242
|
+
};
|
|
243
|
+
}, [activeKey, canFloat, keys.length]);
|
|
244
|
+
const closeLayer = () => {
|
|
245
|
+
const key = activeKey;
|
|
246
|
+
pick(null);
|
|
247
|
+
const row = [...(listRef.current?.children ?? [])].find((r) => (r as HTMLElement).dataset.key === key);
|
|
248
|
+
row?.querySelector<HTMLButtonElement>(".gemboss-ei-rep-toggle")?.focus();
|
|
249
|
+
};
|
|
250
|
+
const floating = popover && canFloat;
|
|
251
|
+
const rootClass = ["gemboss-ei-fld gemboss-ei-rep", touch ? "gemboss-ei-rep--touch" : "", popover ? "gemboss-ei-rep--pop" : ""].filter(Boolean).join(" ");
|
|
193
252
|
|
|
194
253
|
return (
|
|
195
|
-
<div className={
|
|
254
|
+
<div className={rootClass}>
|
|
196
255
|
<label>{label}{keys.length ? <span className="gemboss-ei-rep-count">{keys.length}</span> : null}</label>
|
|
197
256
|
{hint}
|
|
198
257
|
{keys.length === 0 ? (empty ?? <p className="gemboss-ei-hint">No items yet.</p>) : (
|
|
199
258
|
<div className="gemboss-ei-rep-list" ref={listRef}>
|
|
200
259
|
{keys.map((key, i) => {
|
|
201
|
-
const
|
|
260
|
+
const picked = popover && activeKey === key;
|
|
261
|
+
// Inline: its own fold. Popover: the row picked opens in place only where nothing can float.
|
|
262
|
+
const isOpen = popover ? picked && !canFloat : open.has(key);
|
|
202
263
|
const name = title(i) || `Item ${i + 1}`;
|
|
203
264
|
const bodyId = `${uid}-${key}`;
|
|
265
|
+
const src = thumb?.(i);
|
|
204
266
|
return (
|
|
205
|
-
<div key={key} data-key={key} className={drag.dragging === key ? "
|
|
206
|
-
data-drop={drag.dropAt(key)} {...rowAttrs?.(i)}
|
|
267
|
+
<div key={key} data-key={key} className={["gemboss-ei-rep-item", drag.dragging === key ? "is-dragging" : "", picked ? "is-active" : ""].filter(Boolean).join(" ")}
|
|
268
|
+
data-drop={onMove ? drag.dropAt(key) : undefined} {...rowAttrs?.(i)}
|
|
207
269
|
// A list that went off mid-drag (a save started) takes no drop: the move would race that save.
|
|
208
|
-
onDragOver={disabled ? undefined : drag.over(key)} onDrop={disabled ? drag.end : drag.drop}>
|
|
270
|
+
onDragOver={disabled || !onMove ? undefined : drag.over(key)} onDrop={!onMove ? undefined : disabled ? drag.end : drag.drop}>
|
|
209
271
|
<div className="gemboss-ei-rep-head">
|
|
210
272
|
{/* Kept while the list is off (a write in flight), inert: taking it away moved every row's name 16px on each save. */}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
273
|
+
{onMove ? (
|
|
274
|
+
<span className="gemboss-ei-rep-grip" aria-hidden draggable={!disabled} title={disabled ? undefined : "Drag to reorder"}
|
|
275
|
+
onDragStart={disabled ? undefined : (e) => { const row = e.currentTarget.closest(".gemboss-ei-rep-item"); if (row) e.dataTransfer.setDragImage(row, 12, 12); drag.start(key)(e); }}
|
|
276
|
+
onDragEnd={drag.end}>⋮⋮</span>
|
|
277
|
+
) : null}
|
|
278
|
+
<button type="button" className="gemboss-ei-rep-toggle" aria-expanded={popover ? picked : isOpen}
|
|
279
|
+
aria-controls={picked && floating ? `${uid}-pop` : isOpen ? bodyId : undefined}
|
|
280
|
+
aria-keyshortcuts={disabled || !onMove ? undefined : "Alt+ArrowUp Alt+ArrowDown"} onClick={() => toggle(key)}
|
|
216
281
|
onKeyDown={(e) => {
|
|
217
|
-
if (disabled || !e.altKey || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) return;
|
|
282
|
+
if (disabled || !onMove || !e.altKey || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) return;
|
|
218
283
|
const to = e.key === "ArrowUp" ? i - 1 : i + 1;
|
|
219
284
|
if (to < 0 || to >= keys.length) return;
|
|
220
285
|
e.preventDefault();
|
|
221
286
|
refocus.current = { key, from: i };
|
|
222
287
|
onMove(i, to);
|
|
223
288
|
}}>
|
|
289
|
+
{src ? <img className="gemboss-ei-rep-thumb" src={src} alt="" loading="lazy" /> : null}
|
|
224
290
|
<span className="gemboss-ei-rep-title">{name}</span>
|
|
225
291
|
</button>
|
|
226
|
-
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
292
|
+
{onMove || onDuplicate || onRemove ? (
|
|
293
|
+
<span className="gemboss-ei-rep-acts">
|
|
294
|
+
{onMove ? <>
|
|
295
|
+
<button type="button" title="Move up" aria-label={`Move ${label} item ${i + 1} up`} disabled={disabled || i === 0} onClick={() => onMove(i, i - 1)}>↑</button>
|
|
296
|
+
<button type="button" title="Move down" aria-label={`Move ${label} item ${i + 1} down`} disabled={disabled || i === keys.length - 1} onClick={() => onMove(i, i + 1)}>↓</button>
|
|
297
|
+
</> : null}
|
|
298
|
+
{onDuplicate ? (
|
|
299
|
+
<button type="button" title="Duplicate" aria-label={`Duplicate ${label} item ${i + 1}`} disabled={disabled || !canAdd} onClick={() => onDuplicate(i)}><DuplicateIcon /></button>
|
|
300
|
+
) : null}
|
|
301
|
+
{onRemove ? <button type="button" title="Remove" aria-label={`Remove ${label} item ${i + 1}`} disabled={disabled} onClick={() => onRemove(i)}>✕</button> : null}
|
|
302
|
+
</span>
|
|
303
|
+
) : null}
|
|
234
304
|
</div>
|
|
235
305
|
{isOpen ? <div id={bodyId} className="gemboss-ei-rep-body" role="group" aria-label={name}>{body(i)}</div> : null}
|
|
236
306
|
</div>
|
|
@@ -238,7 +308,23 @@ export function RepeaterList({ label, hint, keys, title, body, onMove, onRemove,
|
|
|
238
308
|
})}
|
|
239
309
|
</div>
|
|
240
310
|
)}
|
|
241
|
-
{onAdd ? <button type="button" className="gemboss-ei-rep-add" disabled={disabled || !canAdd} onClick={onAdd}>{addLabel}</button> : null}
|
|
311
|
+
{onAdd ? <button type="button" className="ui-btn gemboss-ei-rep-add" disabled={disabled || !canAdd} onClick={onAdd}>{addLabel}</button> : null}
|
|
312
|
+
{floating && activeKey !== null ? (() => {
|
|
313
|
+
const i = keys.indexOf(activeKey);
|
|
314
|
+
const name = title(i) || `Item ${i + 1}`;
|
|
315
|
+
return (
|
|
316
|
+
// A child of the list in the DOM (Tab order, React context, the host's providers), painted in the top layer.
|
|
317
|
+
<div ref={popRef} id={`${uid}-pop`} className="gemboss-ei-rep-pop gemboss-popover-surface" role="dialog" aria-label={name} tabIndex={-1}
|
|
318
|
+
onKeyDown={(e) => { if (e.key === "Escape") { e.stopPropagation(); closeLayer(); } }}>
|
|
319
|
+
<div className="gemboss-ei-rep-pophead">
|
|
320
|
+
<span className="gemboss-ei-rep-title">{name}</span>
|
|
321
|
+
<span className="gemboss-ei-rep-popat">{i + 1} / {keys.length}</span>
|
|
322
|
+
<button type="button" aria-label={`Close ${name}`} onClick={closeLayer}>✕</button>
|
|
323
|
+
</div>
|
|
324
|
+
{body(i)}
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
})() : null}
|
|
242
328
|
</div>
|
|
243
329
|
);
|
|
244
330
|
}
|
package/src/editor/swatch.ts
CHANGED
|
@@ -43,3 +43,23 @@ export function swatchState(value: string, whenEmpty = '#000000'): SwatchState {
|
|
|
43
43
|
if (!value) return { swatch: whenEmpty, usable: true };
|
|
44
44
|
return SWATCHABLE.test(value) ? { swatch: value, usable: true } : { swatch: '#000000', usable: false };
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/** The theme's colour tokens a value may name (`var(--gem-color-accent)`), as a seller reads them. */
|
|
48
|
+
const TOKEN_NAMES: Record<string, string> = { text: "Text", accent: "Accent", "accent-text": "Accent text", muted: "Muted", background: "Background",
|
|
49
|
+
surface: "Surface", border: "Border", success: "Success", warning: "Warning", danger: "Danger" };
|
|
50
|
+
const tokenName = (t: string) => TOKEN_NAMES[t] ?? t.charAt(0).toUpperCase() + t.slice(1).replace(/-/g, " ");
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* WHY THE SWATCH IS OFF, said where a seller sees it (Claude Design: a disabled control says why). The editors' selection toolbar
|
|
54
|
+
* writes the theme's own colours as tokens (`var(--gem-color-accent)`), so every colour picked there turned its field's swatch grey,
|
|
55
|
+
* with only a tooltip a finger or the keyboard never reaches (inventory of 19/10/2026). Null when the swatch shows the value.
|
|
56
|
+
*/
|
|
57
|
+
export function swatchWhy(value: string): string | null {
|
|
58
|
+
// Exactly when the swatch is off (swatchState's one rule): a padded hex is off too, and says so (review of #4636).
|
|
59
|
+
if (swatchState(value).usable) return null;
|
|
60
|
+
const v = value.trim();
|
|
61
|
+
const token = /^var\(\s*--gem-color-([a-z-]+)\s*\)$/i.exec(v)?.[1]?.toLowerCase();
|
|
62
|
+
if (token) return `Theme color: ${tokenName(token)}. Type a hex to use your own.`;
|
|
63
|
+
if (/^var\(/i.test(v)) return "A theme value. Type a hex to use your own.";
|
|
64
|
+
return "Kept as written: the swatch picks 6-digit hex colors only.";
|
|
65
|
+
}
|