@svgrid/enterprise 3.1.10 → 3.1.12

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.
Files changed (63) hide show
  1. package/LICENSE +17 -13
  2. package/dist/cdn/svgrid-enterprise.svelte-external.js +10 -1
  3. package/dist/designer/assets/{GridFooter-Cu8_yBlQ.js → GridFooter-CKBVItAU.js} +1 -1
  4. package/dist/designer/assets/{GridMenus-BUjkkAU2.js → GridMenus-xnVGSn7N.js} +3 -3
  5. package/dist/designer/assets/SvDateRangeInput-DbfmXqJj.js +1 -0
  6. package/dist/designer/assets/SvDateTimePicker-r4FqmR6R.js +1 -0
  7. package/dist/designer/assets/{SvGrid.helpers-CIrmIo9U.js → SvGrid.helpers-BF2QRcNt.js} +1 -1
  8. package/dist/designer/assets/SvGridCellEditor-od4VbndV.js +4 -0
  9. package/dist/designer/assets/{SvGridChart-Fg43Yi0M.js → SvGridChart-DYtGHM3F.js} +2 -2
  10. package/dist/designer/assets/{SvGridChartBuilder-BqGuA3yU.js → SvGridChartBuilder-BjdQrnz5.js} +1 -1
  11. package/dist/designer/assets/{SvGridChartPanel-BsiXRAnx.js → SvGridChartPanel-Cm9_K7BV.js} +3 -3
  12. package/dist/designer/assets/{SvGridChartView-BgttK0tR.js → SvGridChartView-Bfs0bvDb.js} +1 -1
  13. package/dist/designer/assets/{cell-formatting-BWrW_O0n.js → cell-formatting-JoxA0t65.js} +1 -1
  14. package/dist/designer/assets/{chart-BIl-dIRv.js → chart-Bmu-KAet.js} +1 -1
  15. package/dist/designer/assets/{chart-panel-messages-4cF2vw7L.js → chart-panel-messages-B3y8y3kL.js} +1 -1
  16. package/dist/designer/assets/{chart-summary-B7bTtSCC.js → chart-summary-DxE0R2Bk.js} +1 -1
  17. package/dist/designer/assets/{chart-trend-CLsn5Hgd.js → chart-trend-_gDVw3bP.js} +1 -1
  18. package/dist/designer/assets/{export-format-DGscOjS9.js → export-format-BCuqcexA.js} +1 -1
  19. package/dist/designer/assets/index-B2WZFQ5M.css +1 -0
  20. package/dist/designer/assets/{index-CJodU3tU.js → index-DUaUUFEm.js} +317 -317
  21. package/dist/designer/assets/{src-C_U1NKOY.js → src-hO8VnnI5.js} +1 -1
  22. package/dist/designer/index.html +13 -13
  23. package/dist/fields/BaseTableView.svelte +43 -1
  24. package/dist/fields/LongTextEditor.svelte +6 -1
  25. package/dist/fields/RecordView.svelte +36 -9
  26. package/dist/fields/RowHeader.svelte +22 -3
  27. package/dist/fields/SelectEditor.svelte +8 -1
  28. package/dist/fields/SvBase.svelte +42 -2
  29. package/dist/fields/SvBase.svelte.d.ts +3 -0
  30. package/dist/fields/SvBaseViewBar.svelte +10 -0
  31. package/dist/fields/SvBaseViewBar.svelte.d.ts +5 -0
  32. package/dist/fields/activity.svelte.d.ts +42 -0
  33. package/dist/fields/activity.svelte.js +57 -0
  34. package/dist/fields/base-schema.d.ts +1 -1
  35. package/dist/fields/base-schema.js +7 -0
  36. package/dist/fields/base-sync.d.ts +21 -0
  37. package/dist/fields/base-sync.js +9 -7
  38. package/dist/fields/index.d.ts +1 -1
  39. package/dist/fields/index.js +9 -2
  40. package/dist/node/studio.js +11 -2
  41. package/dist/studio/ui-components.generated.js +9 -0
  42. package/dist/version.d.ts +2 -2
  43. package/dist/version.js +2 -2
  44. package/dist/wc/{GridFooter-f5K0aLe_.js → GridFooter-4T29pfok.js} +1 -1
  45. package/dist/wc/{GridMenus-FtSN_8t9.js → GridMenus-jRuxbYD8.js} +2 -2
  46. package/dist/wc/{SvDateRangeInput-DR6m7bSM.js → SvDateRangeInput-BYNOfYxD.js} +68 -65
  47. package/dist/wc/{SvDateTimePicker-D-vRnaMo.js → SvDateTimePicker-Bq9N0Oiy.js} +57 -53
  48. package/dist/wc/{SvGrid.helpers-B2GEKrxS.js → SvGrid.helpers-tk7gWxcT.js} +1 -1
  49. package/dist/wc/{SvGridCellEditor-B_cLJrsp.js → SvGridCellEditor-Nnl8C3Yj.js} +159 -148
  50. package/dist/wc/{SvGridChart-C6UvNIbN.js → SvGridChart-DrmqEen2.js} +3 -3
  51. package/dist/wc/{SvGridChartBuilder-ikf_5LyW.js → SvGridChartBuilder-XFvW1BdT.js} +3 -3
  52. package/dist/wc/{SvGridChartPanel-Dc_jj7B-.js → SvGridChartPanel-oJJtUKG6.js} +4 -4
  53. package/dist/wc/{SvGridChartView-thkznAOb.js → SvGridChartView-Cetuj70H.js} +2 -2
  54. package/dist/wc/{cell-formatting-hb3vzxhJ.js → cell-formatting-5uEa4Y_0.js} +1 -0
  55. package/dist/wc/{chart-CV5D7_k8.js → chart-CznMeoM7.js} +1 -1
  56. package/dist/wc/{chart-summary-DGx0QhJa.js → chart-summary-COWSPw1p.js} +1 -1
  57. package/dist/wc/{chart-trend-BIRH_xH8.js → chart-trend-Dcn6jrAS.js} +1 -1
  58. package/dist/wc/sv-sheet-element.js +4616 -4589
  59. package/package.json +3 -3
  60. package/dist/designer/assets/SvDateRangeInput-BBRzrQeg.js +0 -1
  61. package/dist/designer/assets/SvDateTimePicker-DCPg7WGN.js +0 -1
  62. package/dist/designer/assets/SvGridCellEditor-CJxThKL_.js +0 -4
  63. package/dist/designer/assets/index-BmvG99kR.css +0 -1
@@ -1,5 +1,5 @@
1
1
  const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./jszip.min-CX2fPs3O.js","./rolldown-runtime-Dd_uD5pT.js","./pdfmake-BpIYFHoG.js","./vfs_fonts-BV-Ul9v2.js","./src-CITpcm6b.js","./chart-export-B6bkQ3X8.js"])))=>i.map(i=>d[i]);
2
- import{i as e,n as t}from"./rolldown-runtime-Dd_uD5pT.js";import{i as n,r,t as i}from"./conditional-formatting-MDWbF8_Y.js";import{a,c as o,d as s,i as c,l,n as u,o as d,s as f,t as p,u as m}from"./export-format-DGscOjS9.js";import{t as h}from"./preload-helper-HclGiUj8.js";var g=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),_=(e,t)=>e>>>t|e<<32-t;function v(e){let t=new TextEncoder().encode(e),n=t.length*8,r=Math.ceil((t.length+9)/64)*64,i=new Uint8Array(r);i.set(t),i[t.length]=128;let a=new DataView(i.buffer);a.setUint32(r-8,Math.floor(n/4294967296)),a.setUint32(r-4,n>>>0);let o=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),s=new Uint32Array(64);for(let e=0;e<r;e+=64){for(let t=0;t<16;t++)s[t]=a.getUint32(e+t*4);for(let e=16;e<64;e++){let t=_(s[e-15],7)^_(s[e-15],18)^s[e-15]>>>3,n=_(s[e-2],17)^_(s[e-2],19)^s[e-2]>>>10;s[e]=s[e-16]+t+s[e-7]+n>>>0}let t=o[0],n=o[1],r=o[2],i=o[3],c=o[4],l=o[5],u=o[6],d=o[7];for(let e=0;e<64;e++){let a=_(c,6)^_(c,11)^_(c,25),o=c&l^~c&u,f=d+a+o+g[e]+s[e]>>>0,p=(_(t,2)^_(t,13)^_(t,22))+(t&n^t&r^n&r)>>>0;d=u,u=l,l=c,c=i+f>>>0,i=r,r=n,n=t,t=f+p>>>0}o[0]=o[0]+t>>>0,o[1]=o[1]+n>>>0,o[2]=o[2]+r>>>0,o[3]=o[3]+i>>>0,o[4]=o[4]+c>>>0,o[5]=o[5]+l>>>0,o[6]=o[6]+u>>>0,o[7]=o[7]+d>>>0}let c=``;for(let e of o)c+=e.toString(16).padStart(8,`0`);return c}function y(e,t){return e===`suite`||t===`grid`}function b(e,t=new Date){return e==null||e.trim()===``?{status:`unset`,valid:!1,edition:`suite`}:v(e.trim().toLowerCase())===`dabe98638cdc5760637bb2a741e65680a6ff0fb590d8b1018c9597650486cb10`?{status:`licensed`,valid:!0,edition:`suite`}:{status:`invalid`,valid:!1,edition:`suite`}}var x=new Set([`svgrid.com`,`www.svgrid.com`]);function S(){let e=globalThis.location?.hostname;return typeof e==`string`&&x.has(e.toLowerCase())}var C=`data-svgrid-enterprise-watermark`,w=`data-svgrid-enterprise-nudged`,ee=`https://svgrid.com/pricing/?utm_source=svgrid-enterprise&utm_medium=watermark`,te=`www.svgrid.com`,ne=5e3,T=700,E=!1,D=null;function O(){S()||(ie(),!(typeof document>`u`||typeof window>`u`)&&(queueMicrotask(()=>j()),D||(D=new MutationObserver(A),D.observe(document.body,{childList:!0,subtree:!0}))))}var k=!1;function A(e){for(let t of e){let e=t.target;if(!(e.nodeType===1&&e.closest?.(`.sv-grid-root[${w}]`))){re();return}}}function re(){if(k)return;k=!0;let e=()=>{k=!1,j()};typeof requestAnimationFrame==`function`?requestAnimationFrame(e):setTimeout(e,16)}function ie(){E||(E=!0,!(typeof console>`u`)&&console.log(`%c @svgrid/enterprise %c User is using a Commercial version which requires a license.
2
+ import{i as e,n as t}from"./rolldown-runtime-Dd_uD5pT.js";import{i as n,r,t as i}from"./conditional-formatting-MDWbF8_Y.js";import{a,c as o,d as s,i as c,l,n as u,o as d,s as f,t as p,u as m}from"./export-format-BCuqcexA.js";import{t as h}from"./preload-helper-HclGiUj8.js";var g=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),_=(e,t)=>e>>>t|e<<32-t;function v(e){let t=new TextEncoder().encode(e),n=t.length*8,r=Math.ceil((t.length+9)/64)*64,i=new Uint8Array(r);i.set(t),i[t.length]=128;let a=new DataView(i.buffer);a.setUint32(r-8,Math.floor(n/4294967296)),a.setUint32(r-4,n>>>0);let o=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),s=new Uint32Array(64);for(let e=0;e<r;e+=64){for(let t=0;t<16;t++)s[t]=a.getUint32(e+t*4);for(let e=16;e<64;e++){let t=_(s[e-15],7)^_(s[e-15],18)^s[e-15]>>>3,n=_(s[e-2],17)^_(s[e-2],19)^s[e-2]>>>10;s[e]=s[e-16]+t+s[e-7]+n>>>0}let t=o[0],n=o[1],r=o[2],i=o[3],c=o[4],l=o[5],u=o[6],d=o[7];for(let e=0;e<64;e++){let a=_(c,6)^_(c,11)^_(c,25),o=c&l^~c&u,f=d+a+o+g[e]+s[e]>>>0,p=(_(t,2)^_(t,13)^_(t,22))+(t&n^t&r^n&r)>>>0;d=u,u=l,l=c,c=i+f>>>0,i=r,r=n,n=t,t=f+p>>>0}o[0]=o[0]+t>>>0,o[1]=o[1]+n>>>0,o[2]=o[2]+r>>>0,o[3]=o[3]+i>>>0,o[4]=o[4]+c>>>0,o[5]=o[5]+l>>>0,o[6]=o[6]+u>>>0,o[7]=o[7]+d>>>0}let c=``;for(let e of o)c+=e.toString(16).padStart(8,`0`);return c}function y(e,t){return e===`suite`||t===`grid`}function b(e,t=new Date){return e==null||e.trim()===``?{status:`unset`,valid:!1,edition:`suite`}:v(e.trim().toLowerCase())===`dabe98638cdc5760637bb2a741e65680a6ff0fb590d8b1018c9597650486cb10`?{status:`licensed`,valid:!0,edition:`suite`}:{status:`invalid`,valid:!1,edition:`suite`}}var x=new Set([`svgrid.com`,`www.svgrid.com`]);function S(){let e=globalThis.location?.hostname;return typeof e==`string`&&x.has(e.toLowerCase())}var C=`data-svgrid-enterprise-watermark`,w=`data-svgrid-enterprise-nudged`,ee=`https://svgrid.com/pricing/?utm_source=svgrid-enterprise&utm_medium=watermark`,te=`www.svgrid.com`,ne=5e3,T=700,E=!1,D=null;function O(){S()||(ie(),!(typeof document>`u`||typeof window>`u`)&&(queueMicrotask(()=>j()),D||(D=new MutationObserver(A),D.observe(document.body,{childList:!0,subtree:!0}))))}var k=!1;function A(e){for(let t of e){let e=t.target;if(!(e.nodeType===1&&e.closest?.(`.sv-grid-root[${w}]`))){re();return}}}function re(){if(k)return;k=!0;let e=()=>{k=!1,j()};typeof requestAnimationFrame==`function`?requestAnimationFrame(e):setTimeout(e,16)}function ie(){E||(E=!0,!(typeof console>`u`)&&console.log(`%c @svgrid/enterprise %c User is using a Commercial version which requires a license.
3
3
  Contact sales@jqwidgets.com for details or visit the license page: https://svgrid.com/pricing/?utm_source=svgrid-enterprise&utm_medium=console`,`background:#4f46e5;color:#fff;padding:2px 6px;border-radius:3px;font-weight:600`,`color:inherit`))}function j(){if(typeof document>`u`)return;let e=document.querySelectorAll(`.sv-grid-root, [role="grid"]`);if(e.length===0){ae();return}document.querySelectorAll(`[${C}="fixed"]`).forEach(e=>e.remove());let t=new Set;e.forEach(e=>{let n=M(e);n&&t.add(n)}),t.forEach(N)}function M(e){let t=e.closest(`.sv-grid-root`);if(t)return t;let n=e;for(;n&&/^(TABLE|THEAD|TBODY|TFOOT|TR|COLGROUP)$/.test(n.tagName);)n=n.parentElement;return n}function N(e){if(e.hasAttribute(w))return;e.setAttribute(w,`1`),getComputedStyle(e).position===`static`&&(e.style.position=`relative`);let t=oe(`grid`);ce(t,!1),e.appendChild(t),se(t)}function ae(){if(typeof document>`u`||document.querySelector(`[${C}="fixed"]`))return;let e=oe(`fixed`);ce(e,!0),document.body.appendChild(e),se(e)}function oe(e){let t=document.createElement(`a`);return t.setAttribute(C,e),t.href=ee,t.target=`_blank`,t.rel=`noopener noreferrer`,t.textContent=te,t.title=`Unlicensed @svgrid/enterprise. Contact sales@jqwidgets.com or visit www.svgrid.com.`,t}function se(e){if(typeof window>`u`){e.remove();return}window.setTimeout(()=>{if(typeof window>`u`){e.remove();return}e.style.opacity=`0`,window.setTimeout(()=>e.remove(),T)},ne)}function ce(e,t){Object.assign(e.style,{position:t?`fixed`:`absolute`,bottom:t?`12px`:`6px`,right:t?`12px`:`8px`,zIndex:`2147483647`,background:`rgba(15,23,42,0.85)`,color:`#fff`,padding:t?`6px 10px`:`4px 8px`,borderRadius:t?`4px`:`3px`,fontFamily:`-apple-system, "Segoe UI", Roboto, sans-serif`,fontSize:t?`11px`:`10px`,lineHeight:`1.2`,textDecoration:`none`,boxShadow:t?`0 2px 12px rgba(0,0,0,0.35)`:`0 1px 4px rgba(0,0,0,0.3)`,border:`1px solid rgba(255,255,255,0.18)`,pointerEvents:`auto`,letterSpacing:`0.02em`,opacity:`1`,transition:`opacity ${T}ms ease`})}var le=`data-svgrid-enterprise-upgrade`,ue=`https://svgrid.com/pricing?ref=in-app&utm_source=svgrid-enterprise&utm_medium=upgrade-prompt`,de=`https://svgrid.com/docs/enterprise/evaluation/?utm_source=svgrid-enterprise&utm_medium=upgrade-prompt`,fe=!1;function P(e,t){if(fe||typeof document>`u`||typeof window>`u`||S()||document.querySelector(`[${le}]`))return;fe=!0;let n=pe(e,t?.expired===!0);document.body.appendChild(n),requestAnimationFrame(()=>{n.style.opacity=`1`,n.style.transform=`translateY(0)`})}function F(e){e.style.opacity=`0`,e.style.transform=`translateY(8px)`,window.setTimeout(()=>e.remove(),220)}function pe(e,t=!1){let n=document.createElement(`div`);n.setAttribute(le,`1`),n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-label`,t?`Your evaluation has ended`:`SvGrid Enterprise`),Object.assign(n.style,{position:`fixed`,bottom:`16px`,right:`16px`,zIndex:`2147483646`,width:`320px`,maxWidth:`calc(100vw - 32px)`,boxSizing:`border-box`,padding:`16px 16px 14px`,background:`#0f172a`,color:`#e2e8f0`,borderRadius:`12px`,border:`1px solid rgba(99,102,241,0.4)`,boxShadow:`0 12px 40px rgba(0,0,0,0.45)`,fontFamily:`-apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`,fontSize:`13px`,lineHeight:`1.45`,opacity:`0`,transform:`translateY(8px)`,transition:`opacity 220ms ease, transform 220ms ease`});let r=e?`<strong style="color:#fff">${me(e?String(e):`This`)}</strong> is part of SvGrid Enterprise.`:`You just used a <strong style="color:#fff">SvGrid Enterprise</strong> feature.`,i=t?`EVALUATION ENDED`:`ENTERPRISE`,a=t?`Your evaluation has ended`:`SvGrid Enterprise`,o=t?`${r} Everything still works, but this notice stays until a
4
4
  license key is set.`:`${r} It keeps working as it is, here and in production. A
5
5
  license key removes the watermark and this notice.`;return n.innerHTML=`
@@ -4,11 +4,11 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>SvGrid Studio - Designer</title>
7
- <script type="module" crossorigin src="./assets/index-CJodU3tU.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-DUaUUFEm.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-Dd_uD5pT.js">
9
9
  <link rel="modulepreload" crossorigin href="./assets/disclose-version-CIm4S9xS.js">
10
- <link rel="modulepreload" crossorigin href="./assets/cell-formatting-BWrW_O0n.js">
11
- <link rel="modulepreload" crossorigin href="./assets/SvGrid.helpers-CIrmIo9U.js">
10
+ <link rel="modulepreload" crossorigin href="./assets/cell-formatting-JoxA0t65.js">
11
+ <link rel="modulepreload" crossorigin href="./assets/SvGrid.helpers-BF2QRcNt.js">
12
12
  <link rel="modulepreload" crossorigin href="./assets/sv-grid-scrollbar-B9N6Wuj0.js">
13
13
  <link rel="modulepreload" crossorigin href="./assets/editor-contract-Bpoa7A5g.js">
14
14
  <link rel="modulepreload" crossorigin href="./assets/date-format-B8JbEtIG.js">
@@ -20,22 +20,22 @@
20
20
  <link rel="modulepreload" crossorigin href="./assets/popover-CUkLsvRX.js">
21
21
  <link rel="modulepreload" crossorigin href="./assets/SvGridDropdown-Cjz0H-fI.js">
22
22
  <link rel="modulepreload" crossorigin href="./assets/dismissable-ChWRPkoa.js">
23
- <link rel="modulepreload" crossorigin href="./assets/SvDateTimePicker-DCPg7WGN.js">
24
- <link rel="modulepreload" crossorigin href="./assets/SvDateRangeInput-BBRzrQeg.js">
23
+ <link rel="modulepreload" crossorigin href="./assets/SvDateTimePicker-r4FqmR6R.js">
24
+ <link rel="modulepreload" crossorigin href="./assets/SvDateRangeInput-DbfmXqJj.js">
25
25
  <link rel="modulepreload" crossorigin href="./assets/focus-trap-V2-6Fy9I.js">
26
26
  <link rel="modulepreload" crossorigin href="./assets/SvTabs-2mHC6oaz.js">
27
27
  <link rel="modulepreload" crossorigin href="./assets/SvMenuList-Dgq2D5VN.js">
28
- <link rel="modulepreload" crossorigin href="./assets/chart-trend-CLsn5Hgd.js">
29
- <link rel="modulepreload" crossorigin href="./assets/chart-BIl-dIRv.js">
28
+ <link rel="modulepreload" crossorigin href="./assets/chart-trend-_gDVw3bP.js">
29
+ <link rel="modulepreload" crossorigin href="./assets/chart-Bmu-KAet.js">
30
30
  <link rel="modulepreload" crossorigin href="./assets/chart-export-B6bkQ3X8.js">
31
- <link rel="modulepreload" crossorigin href="./assets/SvGridChart-Fg43Yi0M.js">
32
- <link rel="modulepreload" crossorigin href="./assets/chart-panel-messages-4cF2vw7L.js">
33
- <link rel="modulepreload" crossorigin href="./assets/SvGridChartPanel-BsiXRAnx.js">
31
+ <link rel="modulepreload" crossorigin href="./assets/SvGridChart-DYtGHM3F.js">
32
+ <link rel="modulepreload" crossorigin href="./assets/chart-panel-messages-B3y8y3kL.js">
33
+ <link rel="modulepreload" crossorigin href="./assets/SvGridChartPanel-Cm9_K7BV.js">
34
34
  <link rel="modulepreload" crossorigin href="./assets/sparkline-C-NcnB2i.js">
35
35
  <link rel="modulepreload" crossorigin href="./assets/server-block-cache-CXJKkJpy.js">
36
36
  <link rel="modulepreload" crossorigin href="./assets/conditional-formatting-MDWbF8_Y.js">
37
- <link rel="modulepreload" crossorigin href="./assets/export-format-DGscOjS9.js">
38
- <link rel="modulepreload" crossorigin href="./assets/src-C_U1NKOY.js">
37
+ <link rel="modulepreload" crossorigin href="./assets/export-format-BCuqcexA.js">
38
+ <link rel="modulepreload" crossorigin href="./assets/src-hO8VnnI5.js">
39
39
  <link rel="stylesheet" crossorigin href="./assets/date-format-DKS-z0xS.css">
40
40
  <link rel="stylesheet" crossorigin href="./assets/SvListBox-BXfyqy4a.css">
41
41
  <link rel="stylesheet" crossorigin href="./assets/SvGridDropdown-CcidB3rm.css">
@@ -45,7 +45,7 @@
45
45
  <link rel="stylesheet" crossorigin href="./assets/SvMenuList-DJ47ba1h.css">
46
46
  <link rel="stylesheet" crossorigin href="./assets/SvGridChart-BgLuPO7I.css">
47
47
  <link rel="stylesheet" crossorigin href="./assets/SvGridChartPanel-4Pc8y5yO.css">
48
- <link rel="stylesheet" crossorigin href="./assets/index-BmvG99kR.css">
48
+ <link rel="stylesheet" crossorigin href="./assets/index-B2WZFQ5M.css">
49
49
  </head>
50
50
  <body>
51
51
  <div id="app"></div>
@@ -9,6 +9,10 @@
9
9
  * With a `runner`, the view bar has the table's Automations; an
10
10
  * automation's "Color the record" action paints the grid while a record
11
11
  * matches its condition.
12
+ *
13
+ * A record someone else changed (read from the database, `base-sync.ts`)
14
+ * has the cells that moved tinted, and a record added elsewhere the whole
15
+ * row, until the record is opened.
12
16
  */
13
17
  import { SvGrid, hasGanttView } from '@svgrid/grid'
14
18
  import SvBaseViewBar from './SvBaseViewBar.svelte'
@@ -16,6 +20,8 @@
16
20
  import BaseChartView from './BaseChartView.svelte'
17
21
  import BaseAutomations from './BaseAutomations.svelte'
18
22
  import { gridOf } from './grid-access'
23
+ import { markAllSeen, unseenIn } from './activity.svelte'
24
+ import { findFieldUser } from './sources.svelte'
19
25
  import { fieldCopyText } from './copy-text'
20
26
  import { recordMenu } from './record-menu'
21
27
  import { matches, type AutomationRun, type AutomationRunner, type BaseAutomation } from './automations.svelte'
@@ -106,7 +112,42 @@
106
112
  }
107
113
  return out
108
114
  })
109
- const conditionalFormats = $derived(highlightFormats.length ? [...((grid.conditionalFormats as any[]) ?? []), ...highlightFormats] : grid.conditionalFormats)
115
+ // ---- Changes made elsewhere ------------------------------------------------
116
+ const UNSEEN = 'color-mix(in srgb, var(--sg-accent, #2563eb) 20%, transparent)'
117
+ // A format per changed column, drawn on the records that changed it and are not seen yet.
118
+ const unseenFormats = $derived.by(() => {
119
+ const tableId = grid.baseTableId
120
+ const getRowId = grid.getRowId
121
+ if (typeof tableId !== 'string' || !tableId || typeof getRowId !== 'function') return []
122
+ const marks = unseenIn({ base: grid.baseScope, tableId })
123
+ if (!marks.size) return []
124
+ const idOf = (row: Record<string, unknown>) => String(getRowId(row, 0))
125
+ const columns = new Set<string>()
126
+ let added = false
127
+ for (const m of marks.values()) {
128
+ if (m.added) added = true
129
+ for (const c of m.columns) columns.add(c)
130
+ }
131
+ const out: any[] = []
132
+ if (added) out.push({ type: 'rule', when: ({ row }: { row: Record<string, unknown> }) => !!marks.get(idOf(row))?.added, background: UNSEEN })
133
+ for (const c of columns) out.push({ type: 'rule', columns: [c], when: ({ row }: { row: Record<string, unknown> }) => !!marks.get(idOf(row))?.columns.has(c), background: UNSEEN })
134
+ return out
135
+ })
136
+ // The view bar's "3 changed by Ana" chip: one name when one person made them all.
137
+ const changed = $derived.by(() => {
138
+ const tableId = grid.baseTableId
139
+ if (typeof tableId !== 'string' || !tableId) return null
140
+ const scope = { base: grid.baseScope, tableId }
141
+ const marks = unseenIn(scope)
142
+ if (!marks.size) return null
143
+ const authors = new Set([...marks.values()].map((m) => m.by ?? ''))
144
+ const only = authors.size === 1 ? [...authors][0] : ''
145
+ const who = only ? (findFieldUser(only)?.name ?? 'someone else') : authors.size === 1 ? 'someone else' : 'others'
146
+ return { label: `${marks.size} changed by ${who}`, onSeen: () => markAllSeen(scope) }
147
+ })
148
+ const conditionalFormats = $derived(
149
+ highlightFormats.length || unseenFormats.length ? [...((grid.conditionalFormats as any[]) ?? []), ...highlightFormats, ...unseenFormats] : grid.conditionalFormats,
150
+ )
110
151
  </script>
111
152
 
112
153
  <div class="sv-fbase" class:is-bare={bare} style:height>
@@ -119,6 +160,7 @@
119
160
  viewTypes={types}
120
161
  {rowHeights}
121
162
  automations={runner && table ? { count: automationList.length, onManage: () => (automationsOpen = true) } : null}
163
+ {changed}
122
164
  />
123
165
  {/if}
124
166
  <div class="sv-fbase-body">
@@ -6,7 +6,7 @@
6
6
  * outside saves, and Escape leaves the cell as it was. With rich text on,
7
7
  * the footer names the markup the cell draws.
8
8
  */
9
- import { portalToBody } from '@svgrid/grid'
9
+ import { createDismissableLayer, portalToBody } from '@svgrid/grid'
10
10
  import { onMount } from 'svelte'
11
11
  import { fieldPropsOf } from './registry.svelte'
12
12
 
@@ -74,7 +74,12 @@
74
74
  window.addEventListener('resize', place)
75
75
  window.addEventListener('scroll', onScroll, true)
76
76
  document.addEventListener('pointerdown', onDown, true)
77
+ // Drawn at the end of the page, outside the record view's dialog: as the
78
+ // top layer it keeps a click in the panel from closing that dialog.
79
+ const layer = createDismissableLayer({ element: () => [panel ?? null, anchor ?? null], onDismiss: () => {}, closeOnEscape: false, closeOnOutside: false })
80
+ layer.activate()
77
81
  return () => {
82
+ layer.release()
78
83
  cancelAnimationFrame(frame)
79
84
  window.removeEventListener('resize', place)
80
85
  window.removeEventListener('scroll', onScroll, true)
@@ -7,7 +7,9 @@
7
7
  * is written through the grid's own edit path, so undo, validation,
8
8
  * onCellValueChange and "last modified" fields behave as in the cell.
9
9
  * Fields hidden in the grid are listed under the others, folded. Beside
10
- * the fields, the record's activity: its changes and its comments.
10
+ * the fields, the record's activity: its changes and its comments. A
11
+ * change made elsewhere (another user, an agent) shows who made it and what
12
+ * the field held before, and opening the record marks it seen.
11
13
  * Previous and next step through the rows in the grid's order.
12
14
  */
13
15
  import { onMount, untrack } from 'svelte'
@@ -16,7 +18,7 @@
16
18
  import FieldInput from './FieldInput.svelte'
17
19
  import { getFieldType } from './registry.svelte'
18
20
  import { cellText, fieldColumns, primaryIdOf, type FieldColumn } from './grid-access'
19
- import { addComment, changesOf, commentsOf, timeAgo } from './activity.svelte'
21
+ import { addComment, changesOf, commentsOf, markSeen, timeAgo, unseenOf } from './activity.svelte'
20
22
  import { findFieldUser } from './sources.svelte'
21
23
 
22
24
  let {
@@ -141,17 +143,36 @@
141
143
 
142
144
  // ---- Activity ---------------------------------------------------------------
143
145
  const allCols = $derived([...columns, ...hidden])
146
+ type FeedItem = { key: string; at: number; by?: string; automation?: string; remote?: boolean; kind: 'edit' | 'added' | 'comment'; field: string; text: string; from?: string }
144
147
  const feed = $derived.by(() => {
145
148
  if (!row) return []
146
- const edits = changesOf(ctrl, row.id).map((ch) => {
149
+ const edits = changesOf(ctrl, row.id).map((ch): FeedItem => {
150
+ if (ch.added) return { key: `a${ch.at}`, at: ch.at, by: ch.by, remote: true, kind: 'added', field: '', text: '' }
147
151
  const c = allCols.find((x) => x.id === ch.columnId)
148
- return { key: `e${ch.at}${ch.columnId}`, at: ch.at, by: ch.by, automation: ch.automation, kind: 'edit' as const, field: c ? label(c) : ch.columnId, text: c ? cellText(asField(c), ch.value, row) : String(ch.value ?? '') }
152
+ const show = (v: unknown) => (c ? cellText(asField(c), v, row) : String(v ?? ''))
153
+ return {
154
+ key: `e${ch.at}${ch.columnId}`,
155
+ at: ch.at,
156
+ by: ch.by,
157
+ automation: ch.automation,
158
+ remote: ch.remote,
159
+ kind: 'edit',
160
+ field: c ? label(c) : ch.columnId,
161
+ text: show(ch.value),
162
+ // What it held before, for a change made elsewhere (nothing to show when it was empty).
163
+ ...('from' in ch && show(ch.from) ? { from: show(ch.from) } : {}),
164
+ }
149
165
  })
150
- const notes = commentsOf(ctrl, row.id).map((n) => ({ key: n.id, at: n.at, by: n.by, automation: undefined, kind: 'comment' as const, field: '', text: n.text }))
166
+ const notes = commentsOf(ctrl, row.id).map((n): FeedItem => ({ key: n.id, at: n.at, by: n.by, kind: 'comment', field: '', text: n.text }))
151
167
  return [...edits, ...notes].sort((a, b) => a.at - b.at)
152
168
  })
153
- const who = (item: { by?: string; automation?: string }) =>
154
- item.automation ? `Automation: ${item.automation}` : item.by ? (findFieldUser(item.by)?.name ?? 'Someone') : 'You'
169
+ const who = (item: FeedItem) =>
170
+ item.automation ? `Automation: ${item.automation}` : item.by ? (findFieldUser(item.by)?.name ?? 'Someone') : item.remote ? 'Someone else' : 'You'
171
+ // Shown here: a change made elsewhere is seen, so the grid stops marking it.
172
+ $effect(() => {
173
+ const id = row?.id
174
+ if (id != null && unseenOf(ctrl, id)) untrack(() => markSeen(ctrl, id))
175
+ })
155
176
  function send() {
156
177
  if (!row || !comment.trim()) return
157
178
  addComment(ctrl, row.id, comment)
@@ -208,8 +229,12 @@
208
229
  <ol class="sv-frv-feed">
209
230
  {#each feed as item (item.key)}
210
231
  <li class="sv-frv-item" class:is-comment={item.kind === 'comment'}>
211
- <div class="sv-frv-item-head"><strong>{who(item)}</strong>{item.kind === 'edit' ? ` changed ${item.field}` : ''}<span class="sv-frv-when">{timeAgo(item.at)}</span></div>
212
- <div class="sv-frv-item-body">{item.text || (item.kind === 'edit' ? '(cleared)' : '')}</div>
232
+ <div class="sv-frv-item-head"><strong>{who(item)}</strong>{item.kind === 'edit' ? ` changed ${item.field}` : item.kind === 'added' ? ' added this record' : ''}<span class="sv-frv-when">{timeAgo(item.at)}</span></div>
233
+ {#if item.kind !== 'added'}
234
+ <div class="sv-frv-item-body">
235
+ {#if item.from !== undefined}<span class="sv-frv-from">{item.from}</span><span class="sv-frv-arrow" aria-label="to">→</span>{/if}{item.text || (item.kind === 'edit' ? '(cleared)' : '')}
236
+ </div>
237
+ {/if}
213
238
  </li>
214
239
  {:else}
215
240
  <li class="sv-frv-none">No changes or comments yet.</li>
@@ -281,6 +306,8 @@
281
306
  .sv-frv-item { font-size: 12px; }
282
307
  .sv-frv-item-head { color: var(--sg-muted, #64748b); }
283
308
  .sv-frv-item-head strong { color: var(--sg-fg, #0f172a); font-weight: 600; }
309
+ .sv-frv-from { color: var(--sg-muted, #64748b); text-decoration: line-through; }
310
+ .sv-frv-arrow { margin: 0 5px; color: var(--sg-muted, #64748b); }
284
311
  .sv-frv-when { margin-inline-start: 6px; opacity: 0.8; }
285
312
  .sv-frv-item-body { margin-top: 2px; white-space: pre-wrap; word-break: break-word; }
286
313
  .sv-frv-item.is-comment .sv-frv-item-body {
@@ -3,18 +3,27 @@
3
3
  * The row-number cell with `expandRows`: the number, which on hover (or
4
4
  * while the row is selected) turns into a checkbox that selects the row,
5
5
  * and an expand button that opens the row as a record. A record with
6
- * comments shows how many; a click opens it at its discussion.
6
+ * comments shows how many; a click opens it at its discussion. A record
7
+ * someone else changed, not opened here since, has a dot in the corner.
7
8
  */
8
9
  import { openRecord } from './editor-request.svelte'
9
- import { commentsOf } from './activity.svelte'
10
+ import { commentsOf, timeAgo, unseenOf } from './activity.svelte'
11
+ import { findFieldUser } from './sources.svelte'
10
12
 
11
13
  let { ctrl, row, rowIndex }: { ctrl: any; row: { id: string; original: unknown }; rowIndex: number } = $props()
12
14
 
13
15
  const selected = $derived(!!ctrl.isRowSelected?.(row.id, row.original))
14
16
  const comments = $derived(commentsOf(ctrl, row.id).length)
17
+ const unseen = $derived(unseenOf(ctrl, row.id))
18
+ const unseenText = $derived.by(() => {
19
+ if (!unseen) return ''
20
+ const who = unseen.by ? (findFieldUser(unseen.by)?.name ?? 'Someone else') : 'Someone else'
21
+ return `${who} ${unseen.added ? 'added' : 'changed'} this record ${timeAgo(unseen.at)}`
22
+ })
15
23
  </script>
16
24
 
17
25
  <span class="sv-frh" class:is-selected={selected} class:has-comments={comments > 0}>
26
+ {#if unseen}<span class="sv-frh-unseen" role="img" aria-label={unseenText} title={unseenText}></span>{/if}
18
27
  <span class="sv-frh-num" aria-hidden="true">{rowIndex + 1}</span>
19
28
  <button
20
29
  type="button"
@@ -61,7 +70,17 @@
61
70
  </span>
62
71
 
63
72
  <style>
64
- .sv-frh { display: flex; align-items: center; gap: 4px; height: 100%; padding: 0 4px 0 8px; box-sizing: border-box; }
73
+ .sv-frh { position: relative; display: flex; align-items: center; gap: 4px; height: 100%; padding: 0 4px 0 8px; box-sizing: border-box; }
74
+ .sv-frh-unseen {
75
+ position: absolute; top: 4px; inset-inline-end: 3px; width: 7px; height: 7px; border-radius: 50%;
76
+ background: var(--sg-accent, #2563eb); box-shadow: 0 0 0 2px var(--sg-bg, #fff);
77
+ animation: sv-frh-unseen-in 1.2s ease-out 2;
78
+ }
79
+ @keyframes sv-frh-unseen-in {
80
+ 0% { box-shadow: 0 0 0 2px var(--sg-bg, #fff), 0 0 0 2px color-mix(in srgb, var(--sg-accent, #2563eb) 60%, transparent); }
81
+ 100% { box-shadow: 0 0 0 2px var(--sg-bg, #fff), 0 0 0 9px transparent; }
82
+ }
83
+ @media (prefers-reduced-motion: reduce) { .sv-frh-unseen { animation: none; } }
65
84
  .sv-frh-num { flex: 1; min-width: 0; text-align: center; color: var(--sg-muted, #64748b); font-variant-numeric: tabular-nums; }
66
85
  .sv-frh-check {
67
86
  display: none; flex: none; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0;
@@ -15,7 +15,7 @@
15
15
  * outside keep what is chosen. Escape on a single select leaves the cell as
16
16
  * it was.
17
17
  */
18
- import { portalToBody } from '@svgrid/grid'
18
+ import { createDismissableLayer, portalToBody } from '@svgrid/grid'
19
19
  import { onMount } from 'svelte'
20
20
  import { fieldPropsOf } from './registry.svelte'
21
21
  import { addSelectOptions } from './field-ops'
@@ -133,7 +133,14 @@
133
133
  window.addEventListener('resize', place)
134
134
  window.addEventListener('scroll', onScroll, true)
135
135
  document.addEventListener('pointerdown', onDown, true)
136
+ // The panel is drawn at the end of the page, outside any dialog the editor
137
+ // sits in (the record view). As the top layer it keeps that dialog from
138
+ // reading a click on an option as a click outside and closing: the editor
139
+ // handles outside clicks and Escape itself.
140
+ const layer = createDismissableLayer({ element: () => [panel ?? null, anchor ?? null], onDismiss: () => {}, closeOnEscape: false, closeOnOutside: false })
141
+ layer.activate()
136
142
  return () => {
143
+ layer.release()
137
144
  cancelAnimationFrame(frame)
138
145
  window.removeEventListener('resize', place)
139
146
  window.removeEventListener('scroll', onScroll, true)
@@ -17,11 +17,16 @@
17
17
  *
18
18
  * A table with a `source` (`baseSource`) loads its records from a database
19
19
  * or an API and writes each change back (`base-sync.ts`); the end of the
20
- * tab bar says when it is loading, saving, or a save failed.
20
+ * tab bar says when it is loading, saving, or a save failed. A change made
21
+ * in the database by someone else (another user, a script, an AI agent)
22
+ * shows up in the page: its cells are tinted and its row marked until the
23
+ * record is opened, and the record's activity says who changed what, from
24
+ * the row's "last modified by" field. `onRemoteChange` hears of each one.
21
25
  */
22
26
  import { onMount, untrack } from 'svelte'
23
27
  import { toast } from '@svgrid/grid'
24
- import { createBaseSync, type BaseSyncStatus } from './base-sync'
28
+ import { createBaseSync, type BaseRemoteChange, type BaseSyncStatus } from './base-sync'
29
+ import { noteRemoteChange } from './activity.svelte'
25
30
  import BaseTableView from './BaseTableView.svelte'
26
31
  import BaseTabs from './BaseTabs.svelte'
27
32
  import { registerLinkedTable, unregisterLinkedTable } from './sources.svelte'
@@ -66,6 +71,7 @@
66
71
  onApiReady,
67
72
  serverRuns,
68
73
  automations = true,
74
+ onRemoteChange,
69
75
  ...grid
70
76
  }: {
71
77
  /** Several tables, shown as tabs. Without it, `data` and `columns` are the one table. */
@@ -86,6 +92,8 @@
86
92
  serverRuns?: () => Promise<ReadonlyArray<AutomationRun>>
87
93
  /** The view bar's Automations; false hides it (automations already set still run). */
88
94
  automations?: boolean
95
+ /** Changes made in the database by someone else, once they are in the page (tables with a `source` that has `realtime`). */
96
+ onRemoteChange?: (changes: BaseRemoteChange[]) => void
89
97
  [prop: string]: any
90
98
  } = $props()
91
99
 
@@ -179,7 +187,39 @@
179
187
  syncState = s
180
188
  },
181
189
  onError: (e) => toast.error(`Could not ${e.action === 'load' ? 'load' : 'save'} ${e.table}: ${e.message}`),
190
+ onRemoteChange: (list) => {
191
+ for (const c of list) noteRemote(c)
192
+ onRemoteChange?.(list)
193
+ },
182
194
  })
195
+
196
+ // Fields the database or the page stamps: a change to one says nothing new.
197
+ const STAMPS = new Set(['createdTime', 'createdBy', 'lastModifiedTime', 'lastModifiedBy', 'autoNumber'])
198
+
199
+ /** Who made a change elsewhere: the record's "last modified by" (or, when added, "created by"). */
200
+ function authorOf(t: BaseTable, record: Row, added: boolean): string | undefined {
201
+ const order = added ? ['createdBy', 'lastModifiedBy'] : ['lastModifiedBy']
202
+ for (const type of order) {
203
+ const d = t.columns.find((x) => x.fieldType === type && x.field != null)
204
+ const v = d ? record[String(d.field)] : undefined
205
+ if (v != null && v !== '') return String(v)
206
+ }
207
+ return undefined
208
+ }
209
+
210
+ /** A change made elsewhere goes into the record's activity, and marks it unseen. */
211
+ function noteRemote(c: BaseRemoteChange) {
212
+ const t = tableOf(c.table)
213
+ if (!t || c.type === 'delete') return
214
+ const scope = { base: baseScope, tableId: t.id }
215
+ const by = authorOf(t, c.record, c.type === 'insert')
216
+ if (c.type === 'insert') return noteRemoteChange(scope, c.id, { by, added: true })
217
+ const fields = (c.fields ?? []).flatMap((f) => {
218
+ const d = t.columns.find((x) => x.field != null && String(x.field) === f.field)
219
+ return d && !STAMPS.has(d.fieldType) ? [{ columnId: colId(d), from: f.from, to: f.to }] : []
220
+ })
221
+ noteRemoteChange(scope, c.id, { by, fields })
222
+ }
183
223
  onMount(() => {
184
224
  void sync.load()
185
225
  return () => {
@@ -1,3 +1,4 @@
1
+ import { type BaseRemoteChange } from './base-sync.js';
1
2
  import { type BaseTable } from './base-tables.js';
2
3
  import type { FieldView, FieldViewType } from './view-model.js';
3
4
  import { type AutomationRun } from './automations.svelte.js';
@@ -23,6 +24,8 @@ type $$ComponentProps = {
23
24
  serverRuns?: () => Promise<ReadonlyArray<AutomationRun>>;
24
25
  /** The view bar's Automations; false hides it (automations already set still run). */
25
26
  automations?: boolean;
27
+ /** Changes made in the database by someone else, once they are in the page (tables with a `source` that has `realtime`). */
28
+ onRemoteChange?: (changes: BaseRemoteChange[]) => void;
26
29
  [prop: string]: any;
27
30
  };
28
31
  declare const SvBase: import("svelte").Component<$$ComponentProps, {}, "tables" | "views" | "activeView" | "activeTable">;
@@ -49,6 +49,8 @@
49
49
  rowHeights?: ReadonlyArray<{ label: string; height: number }>
50
50
  /** The Automations button: how many rules, and what its buttons open. */
51
51
  automations?: { count: number; onManage: () => void } | null
52
+ /** Records someone else changed that nobody here opened yet: what to say, and how to mark them all seen. */
53
+ changed?: { label: string; onSeen: () => void } | null
52
54
  }
53
55
 
54
56
  let {
@@ -58,6 +60,7 @@
58
60
  onViewsChange,
59
61
  viewTypes,
60
62
  automations = null,
63
+ changed = null,
61
64
  rowHeights = [
62
65
  { label: 'Short', height: 34 },
63
66
  { label: 'Medium', height: 56 },
@@ -509,6 +512,11 @@
509
512
  {/if}
510
513
 
511
514
  <span class="sv-fvb-spacer"></span>
515
+ {#if changed}
516
+ <button type="button" class="sv-fvb-btn sv-fvb-changed" title="Mark all as seen" onclick={changed.onSeen}>
517
+ <span class="sv-fvb-changed-dot" aria-hidden="true"></span><span>{changed.label}</span>
518
+ </button>
519
+ {/if}
512
520
  {#if automations}
513
521
  <button type="button" class="sv-fvb-btn" class:is-set={automations.count > 0} onclick={automations.onManage}>
514
522
  {@render glyph(ICON.bolt)}<span>{automations.count ? plural(automations.count, 'automation', 'automations') : 'Automations'}</span>
@@ -554,6 +562,8 @@
554
562
  {/if}
555
563
 
556
564
  <style>
565
+ .sv-fvb-changed { color: var(--sg-fg, #0f172a); }
566
+ .sv-fvb-changed-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--sg-accent, #2563eb); flex: none; }
557
567
  .sv-fvb {
558
568
  container-type: inline-size;
559
569
  display: flex; align-items: center; gap: 2px; flex-wrap: wrap; min-height: 40px; padding: 4px 6px;
@@ -20,6 +20,11 @@ type Props = {
20
20
  count: number;
21
21
  onManage: () => void;
22
22
  } | null;
23
+ /** Records someone else changed that nobody here opened yet: what to say, and how to mark them all seen. */
24
+ changed?: {
25
+ label: string;
26
+ onSeen: () => void;
27
+ } | null;
23
28
  };
24
29
  declare const SvBaseViewBar: import("svelte").Component<Props, {}, "views" | "activeView">;
25
30
  type SvBaseViewBar = ReturnType<typeof SvBaseViewBar>;
@@ -25,8 +25,28 @@ export type RecordChange = {
25
25
  by?: string;
26
26
  /** The automation that made it, by name. */
27
27
  automation?: string;
28
+ /** Made elsewhere (another user, a script, an agent) and read from the database. */
29
+ remote?: boolean;
30
+ /** The record was added elsewhere: one entry, with no field. */
31
+ added?: boolean;
28
32
  columnId: string;
29
33
  value: unknown;
34
+ /** What the field held before, when known (a change made elsewhere). */
35
+ from?: unknown;
36
+ };
37
+ /**
38
+ * A record someone else changed that nobody here has looked at yet: the
39
+ * cells that moved are tinted and the row header shows a dot until the record
40
+ * is opened, or `markAllSeen` runs.
41
+ */
42
+ export type UnseenChange = {
43
+ at: number;
44
+ /** Who changed it, when the record says (its "last modified by" field). */
45
+ by?: string;
46
+ /** The column ids that changed. */
47
+ columns: ReadonlySet<string>;
48
+ /** Added elsewhere: the whole record is new. */
49
+ added?: boolean;
30
50
  };
31
51
  /** A grid, or a table of a base named by id (an automation's change). */
32
52
  export type ActivityScope = object | string | {
@@ -44,6 +64,28 @@ export declare function setRecordComments(next: RecordCommentStore | null): void
44
64
  */
45
65
  export declare function noteChange(scope: ActivityScope, rowId: string, columnId: string, value: unknown, automation?: string): void;
46
66
  export declare function changesOf(scope: ActivityScope, rowId: string): ReadonlyArray<RecordChange>;
67
+ /**
68
+ * Note a change made elsewhere and read from the database: an activity entry
69
+ * per field (with what it held before), credited to `by`, and the record
70
+ * marked unseen. `added` notes a record added elsewhere.
71
+ */
72
+ export declare function noteRemoteChange(scope: ActivityScope, rowId: string, change: {
73
+ by?: string;
74
+ added?: boolean;
75
+ fields?: ReadonlyArray<{
76
+ columnId: string;
77
+ from: unknown;
78
+ to: unknown;
79
+ }>;
80
+ }): void;
81
+ /** The record's change made elsewhere that nobody here has seen, if any. */
82
+ export declare function unseenOf(scope: ActivityScope, rowId: string): UnseenChange | undefined;
83
+ /** Every unseen record of a grid or table, by row id. */
84
+ export declare function unseenIn(scope: ActivityScope): ReadonlyMap<string, UnseenChange>;
85
+ /** The record was looked at: its cells and row header go back to normal. */
86
+ export declare function markSeen(scope: ActivityScope, rowId: string): void;
87
+ /** Every record of a grid or table was looked at. */
88
+ export declare function markAllSeen(scope: ActivityScope): void;
47
89
  export declare function commentsOf(ctrl: any, rowId: string): ReadonlyArray<RecordComment>;
48
90
  export declare function addComment(ctrl: any, rowId: string, text: string): void;
49
91
  /** "just now", "5 minutes ago", "3 hours ago", else the date. */
@@ -13,6 +13,8 @@ import { getCurrentUser } from './sources.svelte.js';
13
13
  let version = $state(0);
14
14
  const changes = { bases: new WeakMap(), grids: new WeakMap() };
15
15
  const memory = { bases: new WeakMap(), grids: new WeakMap() };
16
+ // One entry per record at most: its unseen change, merged as more arrive.
17
+ const unseen = { bases: new WeakMap(), grids: new WeakMap() };
16
18
  // The base of a table named by id alone.
17
19
  const ANY_BASE = {};
18
20
  let store = null;
@@ -64,6 +66,61 @@ export function changesOf(scope, rowId) {
64
66
  void version;
65
67
  return rowsOf(changes, scope, false)?.get(rowId) ?? [];
66
68
  }
69
+ /**
70
+ * Note a change made elsewhere and read from the database: an activity entry
71
+ * per field (with what it held before), credited to `by`, and the record
72
+ * marked unseen. `added` notes a record added elsewhere.
73
+ */
74
+ export function noteRemoteChange(scope, rowId, change) {
75
+ const at = Date.now();
76
+ const fields = change.fields ?? [];
77
+ if (!change.added && !fields.length)
78
+ return;
79
+ const byRow = rowsOf(changes, scope, true);
80
+ const list = byRow.get(rowId) ?? [];
81
+ const by = change.by ? { by: change.by } : {};
82
+ if (change.added)
83
+ list.push({ at, ...by, remote: true, added: true, columnId: '', value: undefined });
84
+ for (const f of fields)
85
+ list.push({ at, ...by, remote: true, columnId: f.columnId, value: copy(f.to), from: copy(f.from) });
86
+ if (list.length > 50)
87
+ list.splice(0, list.length - 50);
88
+ byRow.set(rowId, list);
89
+ const marks = rowsOf(unseen, scope, true);
90
+ const was = marks.get(rowId)?.[0];
91
+ const columns = new Set([...(was?.columns ?? []), ...fields.map((f) => f.columnId)]);
92
+ marks.set(rowId, [{ at, ...by, columns, ...(change.added || was?.added ? { added: true } : {}) }]);
93
+ version += 1;
94
+ }
95
+ const copy = (v) => (Array.isArray(v) ? [...v] : v);
96
+ /** The record's change made elsewhere that nobody here has seen, if any. */
97
+ export function unseenOf(scope, rowId) {
98
+ void version;
99
+ return rowsOf(unseen, scope, false)?.get(rowId)?.[0];
100
+ }
101
+ /** Every unseen record of a grid or table, by row id. */
102
+ export function unseenIn(scope) {
103
+ void version;
104
+ const out = new Map();
105
+ for (const [id, list] of rowsOf(unseen, scope, false) ?? [])
106
+ if (list[0])
107
+ out.set(id, list[0]);
108
+ return out;
109
+ }
110
+ /** The record was looked at: its cells and row header go back to normal. */
111
+ export function markSeen(scope, rowId) {
112
+ const marks = rowsOf(unseen, scope, false);
113
+ if (marks?.delete(rowId))
114
+ version += 1;
115
+ }
116
+ /** Every record of a grid or table was looked at. */
117
+ export function markAllSeen(scope) {
118
+ const marks = rowsOf(unseen, scope, false);
119
+ if (marks?.size) {
120
+ marks.clear();
121
+ version += 1;
122
+ }
123
+ }
67
124
  const contextOf = (ctrl) => ({ tableId: tableIdOf(ctrl), api: ctrl?.buildApi?.() });
68
125
  export function commentsOf(ctrl, rowId) {
69
126
  void version;
@@ -4,7 +4,7 @@
4
4
  * read.
5
5
  *
6
6
  * Column types become field types, helped by the column's name (`email`,
7
- * `phone`, `price`, `notes`, `rating`); an enum, or a text column that keeps
7
+ * `phone`, `price`, `notes`, `rating`, `updated_by`); an enum, or a text column that keeps
8
8
  * repeating a handful of values, becomes a single select with colored
9
9
  * options; a foreign key becomes a link to the table it names, with a field
10
10
  * in that table that links back; a table that only pairs two others