@toolbox-web/grid 3.8.2 → 3.9.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.
Files changed (100) hide show
  1. package/all.d.ts +1 -0
  2. package/all.js +1 -1
  3. package/all.js.map +1 -1
  4. package/index.js +1 -1
  5. package/index.js.map +1 -1
  6. package/lib/core/defaults.d.ts +1 -1
  7. package/lib/core/internal/aria.d.ts +1 -1
  8. package/lib/core/internal/config-manager.d.ts +0 -4
  9. package/lib/core/plugin/base-plugin.d.ts +14 -0
  10. package/lib/core/plugin/plugin-manager.d.ts +5 -1
  11. package/lib/core/plugin/types.d.ts +2 -0
  12. package/lib/core/types.d.ts +6 -0
  13. package/lib/features/registry.js +1 -1
  14. package/lib/plugins/clipboard/clipboard-plugin.d.ts +2 -3
  15. package/lib/plugins/clipboard/index.js +1 -1
  16. package/lib/plugins/clipboard/index.js.map +1 -1
  17. package/lib/plugins/column-virtualization/index.js +1 -1
  18. package/lib/plugins/column-virtualization/index.js.map +1 -1
  19. package/lib/plugins/context-menu/index.js +1 -1
  20. package/lib/plugins/context-menu/index.js.map +1 -1
  21. package/lib/plugins/editing/index.js +1 -1
  22. package/lib/plugins/editing/index.js.map +1 -1
  23. package/lib/plugins/export/index.js +1 -1
  24. package/lib/plugins/export/index.js.map +1 -1
  25. package/lib/plugins/filtering/index.js +1 -1
  26. package/lib/plugins/filtering/index.js.map +1 -1
  27. package/lib/plugins/grouping-columns/index.js +1 -1
  28. package/lib/plugins/grouping-columns/index.js.map +1 -1
  29. package/lib/plugins/grouping-rows/grouping-rows-plugin.d.ts +20 -24
  30. package/lib/plugins/grouping-rows/grouping-rows.d.ts +9 -4
  31. package/lib/plugins/grouping-rows/index.d.ts +1 -1
  32. package/lib/plugins/grouping-rows/index.js +1 -1
  33. package/lib/plugins/grouping-rows/index.js.map +1 -1
  34. package/lib/plugins/grouping-rows/types.d.ts +32 -4
  35. package/lib/plugins/hierarchy/hierarchy-plugin.d.ts +79 -0
  36. package/lib/plugins/hierarchy/hierarchy.d.ts +28 -0
  37. package/lib/plugins/hierarchy/index.d.ts +9 -0
  38. package/lib/plugins/hierarchy/index.js +2 -0
  39. package/lib/plugins/hierarchy/index.js.map +1 -0
  40. package/lib/plugins/hierarchy/types.d.ts +92 -0
  41. package/lib/plugins/master-detail/index.js +1 -1
  42. package/lib/plugins/master-detail/index.js.map +1 -1
  43. package/lib/plugins/multi-sort/index.js +1 -1
  44. package/lib/plugins/multi-sort/index.js.map +1 -1
  45. package/lib/plugins/pinned-columns/index.js +1 -1
  46. package/lib/plugins/pinned-columns/index.js.map +1 -1
  47. package/lib/plugins/pinned-rows/index.js +1 -1
  48. package/lib/plugins/pinned-rows/index.js.map +1 -1
  49. package/lib/plugins/pivot/index.js +1 -1
  50. package/lib/plugins/pivot/index.js.map +1 -1
  51. package/lib/plugins/print/index.js +1 -1
  52. package/lib/plugins/print/index.js.map +1 -1
  53. package/lib/plugins/reorder-columns/index.js +1 -1
  54. package/lib/plugins/reorder-columns/index.js.map +1 -1
  55. package/lib/plugins/responsive/index.js +1 -1
  56. package/lib/plugins/responsive/index.js.map +1 -1
  57. package/lib/plugins/row-drag-drop/index.js +1 -1
  58. package/lib/plugins/row-drag-drop/index.js.map +1 -1
  59. package/lib/plugins/row-drag-drop/row-drag-drop-plugin.d.ts +1 -0
  60. package/lib/plugins/row-drag-drop/types.d.ts +3 -2
  61. package/lib/plugins/selection/index.js +1 -1
  62. package/lib/plugins/selection/index.js.map +1 -1
  63. package/lib/plugins/selection/selection-plugin.d.ts +2 -2
  64. package/lib/plugins/server-side/index.js +1 -1
  65. package/lib/plugins/server-side/index.js.map +1 -1
  66. package/lib/plugins/shell/index.js +1 -1
  67. package/lib/plugins/shell/index.js.map +1 -1
  68. package/lib/plugins/sticky-rows/index.js +1 -1
  69. package/lib/plugins/sticky-rows/index.js.map +1 -1
  70. package/lib/plugins/tooltip/index.js +1 -1
  71. package/lib/plugins/tooltip/index.js.map +1 -1
  72. package/lib/plugins/tree/index.js +1 -1
  73. package/lib/plugins/tree/index.js.map +1 -1
  74. package/lib/plugins/tree/tree-datasource.d.ts +7 -10
  75. package/lib/plugins/tree/tree-plugin.d.ts +23 -3
  76. package/lib/plugins/undo-redo/index.js +1 -1
  77. package/lib/plugins/undo-redo/index.js.map +1 -1
  78. package/lib/plugins/visibility/index.js +1 -1
  79. package/lib/plugins/visibility/index.js.map +1 -1
  80. package/package.json +1 -1
  81. package/umd/grid.all.umd.js +1 -1
  82. package/umd/grid.all.umd.js.map +1 -1
  83. package/umd/grid.umd.js +1 -1
  84. package/umd/grid.umd.js.map +1 -1
  85. package/umd/plugins/clipboard.umd.js +1 -1
  86. package/umd/plugins/clipboard.umd.js.map +1 -1
  87. package/umd/plugins/editing.umd.js +1 -1
  88. package/umd/plugins/editing.umd.js.map +1 -1
  89. package/umd/plugins/grouping-rows.umd.js +1 -1
  90. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  91. package/umd/plugins/hierarchy.umd.js +2 -0
  92. package/umd/plugins/hierarchy.umd.js.map +1 -0
  93. package/umd/plugins/row-drag-drop.umd.js +1 -1
  94. package/umd/plugins/row-drag-drop.umd.js.map +1 -1
  95. package/umd/plugins/selection.umd.js +1 -1
  96. package/umd/plugins/selection.umd.js.map +1 -1
  97. package/umd/plugins/tooltip.umd.js +1 -1
  98. package/umd/plugins/tooltip.umd.js.map +1 -1
  99. package/umd/plugins/tree.umd.js +1 -1
  100. package/umd/plugins/tree.umd.js.map +1 -1
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_tooltip={},t.TbwGrid)}(this,function(t,e){Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});function o(t){return t.scrollWidth>t.clientWidth}function i(t,e){const o=e;return!!o&&t.contains(o)}function r(){return"function"==typeof HTMLElement.prototype?.showPopover}var l=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Tab"]),n="tbw-tooltip-popover",s=class extends e.BaseGridPlugin{name="tooltip";styles='@layer tbw-plugins{.tbw-tooltip-popover{padding:var(--tbw-tooltip-padding,var(--tbw-spacing-sm,.375rem) var(--tbw-spacing-md,.5rem));background:var(--tbw-tooltip-bg,light-dark(#333338,#484850));color:var(--tbw-tooltip-fg,light-dark(#f5f5f5,#f0f0f0));border:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));border-radius:var(--tbw-tooltip-radius,var(--tbw-border-radius,.25rem));box-shadow:none;filter:var(--tbw-tooltip-shadow,drop-shadow(0 4px 4px #00000073));font-family:var(--tbw-font-family,system-ui, sans-serif);font-size:var(--tbw-tooltip-font-size,var(--tbw-font-size-sm,.9285em));max-width:var(--tbw-tooltip-max-width,300px);white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;pointer-events:auto;-webkit-user-select:text;user-select:text;z-index:10001;margin:0}.tbw-tooltip-popover.tbw-tooltip-multiline{white-space:pre;word-wrap:normal;overflow-wrap:normal;max-width:none}.tbw-tooltip-popover:after{content:"";width:var(--tbw-tooltip-arrow-size,14px);height:var(--tbw-tooltip-arrow-size,14px);left:var(--tbw-tooltip-arrow-offset,16px);background:inherit;pointer-events:none;position:absolute}.tbw-tooltip-popover:not(.tbw-tooltip-above):after{top:calc(var(--tbw-tooltip-arrow-size,14px) / -2);border-top:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));border-left:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));clip-path:polygon(0 0,100% 0,0 100%);transform:rotate(45deg)}.tbw-tooltip-popover.tbw-tooltip-above:after{bottom:calc(var(--tbw-tooltip-arrow-size,14px) / -2);border-bottom:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));border-right:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));clip-path:polygon(100% 0,100% 100%,0 100%);transform:rotate(45deg)}@supports (anchor-name:--test){.tbw-tooltip-popover{position-anchor:--tbw-tooltip-anchor;top:calc(anchor(bottom) + 11px);bottom:auto;left:anchor(left)}.tbw-tooltip-popover.tbw-tooltip-above{top:auto;bottom:calc(anchor(top) + 11px)}}}';static manifest={ownedProperties:[{property:"cellTooltip",level:"column",description:'the "cellTooltip" column property'},{property:"headerTooltip",level:"column",description:'the "headerTooltip" column property'}],configRules:[]};#t=null;#e=null;#o=!1;#i=null;#r=null;get#l(){return!1!==this.config.header}get#n(){return!1!==this.config.cell}get#s(){return!1!==this.config.focus}get#a(){return this.config.hideDelay??120}attach(t){super.attach(t)}detach(){this.#p(),null!==this.#r&&(cancelAnimationFrame(this.#r),this.#r=null),this.#h(),this.#t?.remove(),this.#t=null,this.#o=!1,super.detach()}afterRender(){this.#d(),this.#c()}#d(){if(this.#t)return;const t=document.createElement("div");t.className="tbw-tooltip-popover",t.setAttribute("popover","hint"),t.id=n,t.setAttribute("role","tooltip"),t.style.overflow="visible",t.style.margin="0",document.body.appendChild(t),this.#t=t,t.addEventListener("mouseenter",()=>this.#p(),{signal:this.disconnectSignal}),t.addEventListener("mouseleave",()=>this.#u(),{signal:this.disconnectSignal})}#b(t,e){if(this.#t){if(this.#p(),this.#f(),t.style.setProperty("anchor-name","--tbw-tooltip-anchor"),t.setAttribute("aria-describedby",n),this.#e=t,this.#t.textContent=e,this.#t.classList.toggle("tbw-tooltip-multiline",e.includes("\n")),r())try{this.#t.showPopover()}catch{}"undefined"!=typeof CSS&&!0===CSS.supports?.("anchor-name","--test")?this.#t.classList.toggle("tbw-tooltip-above",this.#w(t)):this.#v(t)}}#h(){if(this.#p(),this.#t){if(r())try{this.#t.hidePopover()}catch{}this.#t.classList.remove("tbw-tooltip-above")}this.#f()}get#g(){return null!==this.#e}#u(){this.#p(),this.#a<=0?this.#h():this.#i=setTimeout(()=>{this.#i=null,this.#h()},this.#a)}#p(){null!==this.#i&&(clearTimeout(this.#i),this.#i=null)}#f(){this.#e&&("--tbw-tooltip-anchor"===this.#e.style.getPropertyValue("anchor-name")&&this.#e.style.removeProperty("anchor-name"),this.#e.getAttribute("aria-describedby")===n&&this.#e.removeAttribute("aria-describedby"),this.#e=null)}#w(t){if(!this.#t)return!1;const e=t.getBoundingClientRect(),o=e.top-11,i=window.innerHeight-e.bottom-11;return this.#t.offsetHeight<=o||o>=i}#v(t){if(!this.#t)return;const e=t.getBoundingClientRect(),o=this.#w(t);this.#t.style.position="fixed",this.#t.style.left=`${e.left}px`,o?(this.#t.style.top="",this.#t.style.bottom=window.innerHeight-e.top+11+"px"):(this.#t.style.top=`${e.bottom+11}px`,this.#t.style.bottom=""),this.#t.classList.toggle("tbw-tooltip-above",o)}#c(){if(this.#o)return;const t=this.gridElement?.querySelector(".tbw-grid-root");t&&(this.#o=!0,t.addEventListener("mouseover",t=>this.#m(t),{signal:this.disconnectSignal}),t.addEventListener("mouseout",t=>this.#y(t),{signal:this.disconnectSignal}),document.addEventListener("keydown",t=>this.#E(t),{capture:!0,signal:this.disconnectSignal}),this.gridElement.addEventListener("keydown",t=>this.#T(t),{signal:this.disconnectSignal}))}#m(t){const e=t.target;if(!e?.closest)return;const o=e.closest('[part~="header-cell"]');if(o&&this.#l)return void(i(o,t.relatedTarget)||this.#x(o));const r=e.closest("[data-row][data-col]");if(r&&this.#n&&!r.style.getPropertyValue("anchor-name")){if(i(r,t.relatedTarget))return;this.#C(r)}}#y(t){const e=t.target;if(!e?.closest)return;const o=e.closest('[part~="header-cell"], [data-row][data-col]');o&&(i(o,t.relatedTarget)||this.#u())}#E(t){"Escape"===t.key&&this.#g&&this.#h()}#T(t){this.#s&&this.#n&&"Escape"!==t.key&&(l.has(t.key)?(null!==this.#r&&cancelAnimationFrame(this.#r),this.#r=requestAnimationFrame(()=>{this.#r=null,this.#P()})):this.#h())}#P(){const t=this.gridElement?.querySelector(".cell-focus");if(!t||!t.hasAttribute("data-row"))return void this.#h();if(t===this.#e)return;if(t.style.getPropertyValue("anchor-name"))return void this.#h();const e=this.#e;this.#C(t),this.#e===e&&this.#h()}#x(t){const e=parseInt(t.getAttribute("data-col")??"-1",10);if(e<0)return;const i=this.visibleColumns[e];if(!i)return;const r=function(t,e){const i=t.headerTooltip;if(!1===i)return null;if("string"==typeof i)return i;if("function"==typeof i)return i({column:t,value:t.header??t.field});const r=e.querySelector("span:first-child")??e;return o(r)&&r.textContent?.trim()||null}(i,t);r&&this.#b(t,r)}#C(t){const e=parseInt(t.getAttribute("data-row")??"-1",10),i=parseInt(t.getAttribute("data-col")??"-1",10);if(e<0||i<0)return;const r=this.visibleColumns[i];if(!r)return;const l=this.rows[e],n=l?.[r.field],s=function(t,e,i,r,l){const n=t.cellTooltip;return!1===n?null:"string"==typeof n?n:"function"==typeof n?n({value:r,row:i,column:t,field:t.field,grid:l}):o(e)&&e.textContent?.trim()||null}(r,t,l,n,this.grid);s&&this.#b(t,s)}};t.TooltipPlugin=s});
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/plugin/base-plugin"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_tooltip={},t.TbwGrid)}(this,function(t,e){Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});function o(t){return t.scrollWidth>t.clientWidth}function i(t,e){const o=e;return!!o&&t.contains(o)}function r(){return"function"==typeof HTMLElement.prototype?.showPopover}var l=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Tab"]),n="tbw-tooltip-popover",s=class extends e.BaseGridPlugin{name="tooltip";styles='@layer tbw-plugins{.tbw-tooltip-popover{padding:var(--tbw-tooltip-padding,var(--tbw-spacing-sm,.375rem) var(--tbw-spacing-md,.5rem));background:var(--tbw-tooltip-bg,light-dark(#333338,#484850));color:var(--tbw-tooltip-fg,light-dark(#f5f5f5,#f0f0f0));border:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));border-radius:var(--tbw-tooltip-radius,var(--tbw-border-radius,.25rem));box-shadow:none;filter:var(--tbw-tooltip-shadow,drop-shadow(0 4px 4px #00000073));font-family:var(--tbw-font-family,system-ui, sans-serif);font-size:var(--tbw-tooltip-font-size,var(--tbw-font-size-sm,.9285em));max-width:var(--tbw-tooltip-max-width,300px);white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;pointer-events:auto;-webkit-user-select:text;user-select:text;z-index:10001;margin:0}.tbw-tooltip-popover.tbw-tooltip-multiline{white-space:pre;word-wrap:normal;overflow-wrap:normal;max-width:none}.tbw-tooltip-popover:after{content:"";width:var(--tbw-tooltip-arrow-size,14px);height:var(--tbw-tooltip-arrow-size,14px);left:var(--tbw-tooltip-arrow-offset,16px);background:inherit;pointer-events:none;position:absolute}.tbw-tooltip-popover:not(.tbw-tooltip-above):after{top:calc(var(--tbw-tooltip-arrow-size,14px) / -2);border-top:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));border-left:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));clip-path:polygon(0 0,100% 0,0 100%);transform:rotate(45deg)}.tbw-tooltip-popover.tbw-tooltip-above:after{bottom:calc(var(--tbw-tooltip-arrow-size,14px) / -2);border-bottom:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));border-right:1px solid var(--tbw-tooltip-border,light-dark(#222226,#6a6a72));clip-path:polygon(100% 0,100% 100%,0 100%);transform:rotate(45deg)}@supports (anchor-name:--test){.tbw-tooltip-popover{position-anchor:--tbw-tooltip-anchor;top:calc(anchor(bottom) + 11px);bottom:auto;left:anchor(left)}.tbw-tooltip-popover.tbw-tooltip-above{top:auto;bottom:calc(anchor(top) + 11px)}}}';static manifest={ownedProperties:[{property:"cellTooltip",level:"column",description:'the "cellTooltip" column property'},{property:"headerTooltip",level:"column",description:'the "headerTooltip" column property'}],configRules:[]};#t=null;#e=null;#o=!1;#i=null;#r=null;get#l(){return!1!==this.config.header}get#n(){return!1!==this.config.cell}get#s(){return!1!==this.config.focus}get#a(){return this.config.hideDelay??120}attach(t){super.attach(t)}detach(){this.#p(),null!==this.#r&&(cancelAnimationFrame(this.#r),this.#r=null),this.#h(),this.#t?.remove(),this.#t=null,this.#o=!1,super.detach()}afterRender(){this.#d(),this.#c()}#d(){if(this.#t)return;const t=document.createElement("div");t.className="tbw-tooltip-popover",t.setAttribute("popover","hint"),t.id=n,t.setAttribute("role","tooltip"),t.style.overflow="visible",t.style.margin="0",document.body.appendChild(t),this.#t=t,t.addEventListener("mouseenter",()=>this.#p(),{signal:this.disconnectSignal}),t.addEventListener("mouseleave",()=>this.#u(),{signal:this.disconnectSignal}),t.addEventListener("wheel",t=>this.#b(t),{passive:!1,signal:this.disconnectSignal})}#b(t){const e=this.#e;if(this.#h(),!e?.isConnected)return;const o=new WheelEvent("wheel",{deltaX:t.deltaX,deltaY:t.deltaY,deltaZ:t.deltaZ,deltaMode:t.deltaMode,clientX:t.clientX,clientY:t.clientY,shiftKey:t.shiftKey,ctrlKey:t.ctrlKey,altKey:t.altKey,metaKey:t.metaKey,bubbles:!0,cancelable:!0,composed:!0});e.dispatchEvent(o)||t.preventDefault()}#f(t,e){if(this.#t){if(this.#p(),this.#w(),t.style.setProperty("anchor-name","--tbw-tooltip-anchor"),t.setAttribute("aria-describedby",n),this.#e=t,this.#t.textContent=e,this.#t.classList.toggle("tbw-tooltip-multiline",e.includes("\n")),r())try{this.#t.showPopover()}catch{}"undefined"!=typeof CSS&&!0===CSS.supports?.("anchor-name","--test")?this.#t.classList.toggle("tbw-tooltip-above",this.#v(t)):this.#g(t)}}#h(){if(this.#p(),this.#t){if(r())try{this.#t.hidePopover()}catch{}this.#t.classList.remove("tbw-tooltip-above")}this.#w()}get#m(){return null!==this.#e}#u(){this.#p(),this.#a<=0?this.#h():this.#i=setTimeout(()=>{this.#i=null,this.#h()},this.#a)}#p(){null!==this.#i&&(clearTimeout(this.#i),this.#i=null)}#w(){this.#e&&("--tbw-tooltip-anchor"===this.#e.style.getPropertyValue("anchor-name")&&this.#e.style.removeProperty("anchor-name"),this.#e.getAttribute("aria-describedby")===n&&this.#e.removeAttribute("aria-describedby"),this.#e=null)}#v(t){if(!this.#t)return!1;const e=t.getBoundingClientRect(),o=e.top-11,i=window.innerHeight-e.bottom-11;return this.#t.offsetHeight<=o||o>=i}#g(t){if(!this.#t)return;const e=t.getBoundingClientRect(),o=this.#v(t);this.#t.style.position="fixed",this.#t.style.left=`${e.left}px`,o?(this.#t.style.top="",this.#t.style.bottom=window.innerHeight-e.top+11+"px"):(this.#t.style.top=`${e.bottom+11}px`,this.#t.style.bottom=""),this.#t.classList.toggle("tbw-tooltip-above",o)}#c(){if(this.#o)return;const t=this.gridElement?.querySelector(".tbw-grid-root");t&&(this.#o=!0,t.addEventListener("mouseover",t=>this.#y(t),{signal:this.disconnectSignal}),t.addEventListener("mouseout",t=>this.#E(t),{signal:this.disconnectSignal}),document.addEventListener("keydown",t=>this.#T(t),{capture:!0,signal:this.disconnectSignal}),this.gridElement.addEventListener("keydown",t=>this.#x(t),{signal:this.disconnectSignal}))}#y(t){const e=t.target;if(!e?.closest)return;const o=e.closest('[part~="header-cell"]');if(o&&this.#l)return void(i(o,t.relatedTarget)||this.#C(o));const r=e.closest("[data-row][data-col]");if(r&&this.#n&&!r.style.getPropertyValue("anchor-name")){if(i(r,t.relatedTarget))return;this.#P(r)}}#E(t){const e=t.target;if(!e?.closest)return;const o=e.closest('[part~="header-cell"], [data-row][data-col]');o&&(i(o,t.relatedTarget)||this.#u())}#T(t){"Escape"===t.key&&this.#m&&this.#h()}#x(t){this.#s&&this.#n&&"Escape"!==t.key&&(l.has(t.key)?(null!==this.#r&&cancelAnimationFrame(this.#r),this.#r=requestAnimationFrame(()=>{this.#r=null,this.#A()})):this.#h())}#A(){const t=this.gridElement?.querySelector(".cell-focus");if(!t||!t.hasAttribute("data-row"))return void this.#h();if(t===this.#e)return;if(t.style.getPropertyValue("anchor-name"))return void this.#h();const e=this.#e;this.#P(t),this.#e===e&&this.#h()}#C(t){const e=parseInt(t.getAttribute("data-col")??"-1",10);if(e<0)return;const i=this.visibleColumns[e];if(!i)return;const r=function(t,e){const i=t.headerTooltip;if(!1===i)return null;if("string"==typeof i)return i;if("function"==typeof i)return i({column:t,value:t.header??t.field});const r=e.querySelector("span:first-child")??e;return o(r)&&r.textContent?.trim()||null}(i,t);r&&this.#f(t,r)}#P(t){const e=parseInt(t.getAttribute("data-row")??"-1",10),i=parseInt(t.getAttribute("data-col")??"-1",10);if(e<0||i<0)return;const r=this.visibleColumns[i];if(!r)return;const l=this.rows[e],n=l?.[r.field],s=function(t,e,i,r,l){const n=t.cellTooltip;return!1===n?null:"string"==typeof n?n:"function"==typeof n?n({value:r,row:i,column:t,field:t.field,grid:l}):o(e)&&e.textContent?.trim()||null}(r,t,l,n,this.grid);s&&this.#f(t,s)}};t.TooltipPlugin=s});
2
2
  //# sourceMappingURL=tooltip.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.umd.js","names":[],"sources":["../../../../../libs/grid/src/lib/plugins/tooltip/tooltip.css?inline","../../../../../libs/grid/src/lib/plugins/tooltip/tooltip-plugin.ts"],"sourcesContent":["@layer tbw-plugins {\n .tbw-tooltip-popover {\n /* Reset browser popover defaults — overflow:visible set inline for UA specificity */\n margin: 0;\n padding: var(--tbw-tooltip-padding, var(--tbw-spacing-sm, 0.375rem) var(--tbw-spacing-md, 0.5rem));\n background: var(--tbw-tooltip-bg, light-dark(#333338, #484850));\n color: var(--tbw-tooltip-fg, light-dark(#f5f5f5, #f0f0f0));\n border: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n border-radius: var(--tbw-tooltip-radius, var(--tbw-border-radius, 0.25rem));\n box-shadow: none;\n filter: var(--tbw-tooltip-shadow, drop-shadow(0 4px 4px rgba(0, 0, 0, 0.45)));\n font-family: var(--tbw-font-family, system-ui, sans-serif);\n font-size: var(--tbw-tooltip-font-size, var(--tbw-font-size-sm, 0.9285em));\n max-width: var(--tbw-tooltip-max-width, 300px);\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n /* WCAG 2.2 SC 1.4.13 \"Hoverable\": the pointer must be able to rest on the\n tooltip without dismissing it, so long or truncated content can be read,\n magnified, and selected. The plugin's grace-period hide covers the gap\n between the cell and the popover. */\n pointer-events: auto;\n user-select: text;\n z-index: 10001;\n }\n\n /* Multi-line content (text contains `\\n`): preserve author line breaks\n verbatim and let the popover grow horizontally instead of wrapping\n mid-line. `max-width` is lifted so long lines aren't broken. */\n .tbw-tooltip-popover.tbw-tooltip-multiline {\n white-space: pre;\n max-width: none;\n word-wrap: normal;\n overflow-wrap: normal;\n }\n\n /* Arrow — a 14×14 box rotated 45° to form a diamond, with its center\n pinned to the popover edge. Without `clip-path` the diamond's inner\n half sits *inside* the popover, painting `background: inherit` over\n the first ~7px of text content. The clip-path keeps only the\n pre-rotation triangle whose three corners rotate to the outside of\n the popover (the visible \"V\"), so the inner half is removed before\n it can overlap the content. clip-path is applied in the element's\n own (pre-transform) coordinate space per the CSS Transforms spec,\n which is why the polygon is expressed in the unrotated square. */\n .tbw-tooltip-popover::after {\n content: '';\n position: absolute;\n width: var(--tbw-tooltip-arrow-size, 14px);\n height: var(--tbw-tooltip-arrow-size, 14px);\n left: var(--tbw-tooltip-arrow-offset, 16px);\n background: inherit;\n pointer-events: none;\n }\n\n /* Arrow below-anchor default: diamond points up from the top edge.\n Pre-rotation: top-left, top-right, bottom-left → after rotate(45deg)\n these become the top, right, and left points of the diamond, i.e.\n the upper \"V\" that pokes above the popover. The bottom-right corner\n (which would land inside the popover) is clipped away. */\n .tbw-tooltip-popover:not(.tbw-tooltip-above)::after {\n top: calc(var(--tbw-tooltip-arrow-size, 14px) / -2);\n transform: rotate(45deg);\n border-top: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n border-left: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n clip-path: polygon(0 0, 100% 0, 0 100%);\n }\n\n /* Above-anchor: diamond points down from the bottom edge. Mirror of\n the below-anchor clip — keep the corners (top-right, bottom-right,\n bottom-left) that rotate to the right, bottom, and left points of\n the diamond, i.e. the lower \"Λ\" that pokes below the popover. */\n .tbw-tooltip-popover.tbw-tooltip-above::after {\n bottom: calc(var(--tbw-tooltip-arrow-size, 14px) / -2);\n transform: rotate(45deg);\n border-bottom: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n border-right: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n clip-path: polygon(100% 0, 100% 100%, 0 100%);\n }\n\n /* CSS Anchor Positioning — modern browsers.\n Placement is chosen in JS (`#prefersAbove`) rather than by\n `position-try-fallbacks`: the browser's flip is invisible to script, so the\n arrow-direction class could disagree with where the popover was painted —\n most visibly when re-hovering while a previous tooltip is still open. */\n @supports (anchor-name: --test) {\n .tbw-tooltip-popover {\n position-anchor: --tbw-tooltip-anchor;\n top: calc(anchor(bottom) + 11px);\n bottom: auto;\n left: anchor(left);\n }\n\n .tbw-tooltip-popover.tbw-tooltip-above {\n top: auto;\n bottom: calc(anchor(top) + 11px);\n }\n }\n}\n","/**\n * Tooltip Plugin\n *\n * Shows styled popover tooltips on header and data cells when text\n * overflows (ellipsis). Uses the Popover API (`popover=\"hint\"`) with\n * CSS anchor positioning for consistent, themed placement.\n *\n * Supports per-column overrides via `cellTooltip` and `headerTooltip`\n * on column config.\n */\n\nimport type { GridElement, PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { CellRenderContext, ColumnConfig, HeaderLabelContext } from '../../core/types';\nimport tooltipStyles from './tooltip.css?inline';\nimport type { TooltipConfig } from './types';\n\n// #region Helpers\n\n/** Check if an element's text content overflows its visible width. */\nfunction isOverflowing(el: HTMLElement): boolean {\n return el.scrollWidth > el.clientWidth;\n}\n\n/**\n * True when a delegated pointer event merely crossed a child boundary *inside*\n * `cell` rather than entering or leaving it.\n *\n * `mouseover`/`mouseout` fire on every element boundary and bubble, so a\n * delegated handler sees one event per child. `relatedTarget` is what recovers\n * `mouseenter`/`mouseleave` semantics from that stream.\n */\nfunction isSameCellCrossing(cell: HTMLElement, related: EventTarget | null): boolean {\n const node = related as Node | null;\n return !!node && cell.contains(node);\n}\n\n/**\n * Resolve the tooltip text for a cell.\n * Returns the text to show, or `null` to suppress.\n */\nfunction resolveCellTooltip(\n column: ColumnConfig,\n cell: HTMLElement,\n row: unknown,\n value: unknown,\n grid?: GridElement,\n): string | null {\n const spec = column.cellTooltip;\n\n if (spec === false) return null;\n if (typeof spec === 'string') return spec;\n if (typeof spec === 'function') {\n const ctx: CellRenderContext = { value, row, column, field: column.field, grid: grid as any };\n return spec(ctx);\n }\n\n // Default: show textContent only when overflowing\n if (isOverflowing(cell)) {\n return cell.textContent?.trim() || null;\n }\n\n return null;\n}\n\n/**\n * Resolve the tooltip text for a header cell.\n * Returns the text to show, or `null` to suppress.\n */\nfunction resolveHeaderTooltip(column: ColumnConfig, headerCell: HTMLElement): string | null {\n const spec = column.headerTooltip;\n\n if (spec === false) return null;\n if (typeof spec === 'string') return spec;\n if (typeof spec === 'function') {\n const ctx: HeaderLabelContext = {\n column,\n value: column.header ?? column.field,\n };\n return spec(ctx);\n }\n\n // Default: show header text only when overflowing\n const labelSpan = headerCell.querySelector('span:first-child') as HTMLElement | null;\n const target = labelSpan ?? headerCell;\n\n if (isOverflowing(target)) {\n return target.textContent?.trim() || null;\n }\n\n return null;\n}\n\n/** Runtime check — happy-dom and older browsers may lack Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\n/** Runtime check for CSS anchor positioning. */\nfunction supportsAnchor(): boolean {\n return typeof CSS !== 'undefined' && CSS.supports?.('anchor-name', '--test') === true;\n}\n\n/** Keys that move the virtual cell focus and should therefore re-target the tooltip. */\nconst NAVIGATION_KEYS = new Set([\n 'ArrowUp',\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n 'Home',\n 'End',\n 'PageUp',\n 'PageDown',\n 'Tab',\n]);\n\n/** Default grace period (ms) allowing the pointer to travel onto the tooltip. */\nconst DEFAULT_HIDE_DELAY_MS = 120;\n\n/** Gap (px) between the anchor cell and the popover, leaving room for the arrow. */\nconst ARROW_GAP_PX = 11;\n\n/** `id` of the shared popover, referenced by `aria-describedby` on the anchor. */\nconst POPOVER_ID = 'tbw-tooltip-popover';\n// #endregion\n\n// #region TooltipPlugin\n/**\n * Tooltip Plugin for tbw-grid\n *\n * Shows styled popover tooltips when header or cell text overflows its\n * container. Uses the Popover API with CSS anchor positioning for\n * consistent themed appearance across light and dark modes.\n *\n * ## Installation\n *\n * ```ts\n * import { TooltipPlugin } from '@toolbox-web/grid/plugins/tooltip';\n * ```\n *\n * @example Default — auto-tooltip on overflow\n * ```ts\n * grid.gridConfig = {\n * plugins: [new TooltipPlugin()],\n * };\n * ```\n *\n * @example Header-only tooltips\n * ```ts\n * grid.gridConfig = {\n * plugins: [new TooltipPlugin({ cell: false })],\n * };\n * ```\n *\n * @example Per-column overrides\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', cellTooltip: (ctx) => `${ctx.row.first} ${ctx.row.last}` },\n * { field: 'actions', cellTooltip: false },\n * { field: 'revenue', headerTooltip: 'Total revenue in USD (before tax)' },\n * ],\n * plugins: [new TooltipPlugin()],\n * };\n * ```\n *\n * ## Accessibility\n *\n * Conforms to WCAG 2.2 SC 1.4.13 (Content on Hover or Focus) out of the box:\n * the tooltip is **dismissible** with `Escape`, **hoverable** (the pointer can\n * rest on it to read or copy the text), **persistent** (never hidden on a\n * timer), and appears on **keyboard focus** as cell focus moves. Pointer\n * interaction never triggers the focus path, so mouse behaviour is unchanged.\n * See {@link TooltipConfig.focus} and {@link TooltipConfig.hideDelay} to tune it.\n *\n * @category Plugins\n * @since 1.28.0\n */\nexport class TooltipPlugin extends BaseGridPlugin<TooltipConfig> {\n readonly name = 'tooltip';\n override readonly styles = tooltipStyles;\n\n static override readonly manifest: PluginManifest<TooltipConfig> = {\n ownedProperties: [\n { property: 'cellTooltip', level: 'column', description: 'the \"cellTooltip\" column property' },\n { property: 'headerTooltip', level: 'column', description: 'the \"headerTooltip\" column property' },\n ],\n configRules: [],\n };\n\n /** The shared popover element for all tooltips. */\n #popoverEl: HTMLElement | null = null;\n\n /** The cell currently acting as CSS anchor. */\n #anchorCell: HTMLElement | null = null;\n\n /** Whether delegated listeners are bound. */\n #bound = false;\n\n /** Pending grace-period hide, cancelled when the pointer enters the tooltip. */\n #hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** Pending focus-driven retarget, so rapid arrow-key repeats coalesce. */\n #focusFrame: number | null = null;\n\n /** Whether header tooltips are enabled globally. */\n get #headerEnabled(): boolean {\n return this.config.header !== false;\n }\n\n /** Whether cell tooltips are enabled globally. */\n get #cellEnabled(): boolean {\n return this.config.cell !== false;\n }\n\n /** Whether keyboard focus shows a tooltip (WCAG 2.2 SC 1.4.13). */\n get #focusEnabled(): boolean {\n return this.config.focus !== false;\n }\n\n /** Grace period before hiding, allowing the pointer to reach the tooltip. */\n get #hideDelay(): number {\n return this.config.hideDelay ?? DEFAULT_HIDE_DELAY_MS;\n }\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n }\n\n override detach(): void {\n this.#cancelPendingHide();\n if (this.#focusFrame !== null) {\n cancelAnimationFrame(this.#focusFrame);\n this.#focusFrame = null;\n }\n this.#hideTooltip();\n this.#popoverEl?.remove();\n this.#popoverEl = null;\n this.#bound = false;\n super.detach();\n }\n\n override afterRender(): void {\n this.#ensurePopover();\n this.#bindEvents();\n }\n\n // #region Popover Lifecycle\n\n /** Create the shared popover element (once). */\n #ensurePopover(): void {\n if (this.#popoverEl) return;\n const el = document.createElement('div');\n el.className = 'tbw-tooltip-popover';\n el.setAttribute('popover', 'hint');\n el.id = POPOVER_ID;\n el.setAttribute('role', 'tooltip');\n // Override UA popover defaults that @layer CSS cannot beat\n el.style.overflow = 'visible';\n el.style.margin = '0';\n document.body.appendChild(el);\n this.#popoverEl = el;\n\n // SC 1.4.13 \"Hoverable\" — the pointer must be able to rest on the tooltip\n // without dismissing it, so its content can be read, magnified, or copied.\n el.addEventListener('mouseenter', () => this.#cancelPendingHide(), { signal: this.disconnectSignal });\n el.addEventListener('mouseleave', () => this.#scheduleHide(), { signal: this.disconnectSignal });\n }\n\n /** Show the popover anchored to `cell` with the given `text`. */\n #showTooltip(cell: HTMLElement, text: string): void {\n if (!this.#popoverEl) return;\n this.#cancelPendingHide();\n\n // Move the CSS anchor to the hovered cell\n this.#clearAnchor();\n cell.style.setProperty('anchor-name', '--tbw-tooltip-anchor');\n cell.setAttribute('aria-describedby', POPOVER_ID);\n this.#anchorCell = cell;\n\n // Set content (always textContent — safe, no XSS).\n // Multi-line content uses `white-space: pre` so author-supplied line\n // breaks survive verbatim and the popover grows to fit; single-line\n // content keeps `pre-wrap` so long values wrap inside `max-width`.\n this.#popoverEl.textContent = text;\n this.#popoverEl.classList.toggle('tbw-tooltip-multiline', text.includes('\\n'));\n\n // Show via Popover API\n if (supportsPopover()) {\n try {\n this.#popoverEl.showPopover();\n } catch {\n /* already shown */\n }\n }\n\n if (supportsAnchor()) {\n this.#popoverEl.classList.toggle('tbw-tooltip-above', this.#prefersAbove(cell));\n } else {\n this.#positionFallback(cell);\n }\n }\n\n /** Hide the popover and clear the anchor reference. */\n #hideTooltip(): void {\n this.#cancelPendingHide();\n if (this.#popoverEl) {\n if (supportsPopover()) {\n try {\n this.#popoverEl.hidePopover();\n } catch {\n /* already hidden */\n }\n }\n this.#popoverEl.classList.remove('tbw-tooltip-above');\n }\n this.#clearAnchor();\n }\n\n /** Whether the popover is currently anchored to a cell (i.e. visible). */\n get #isVisible(): boolean {\n return this.#anchorCell !== null;\n }\n\n /**\n * Hide after the configured grace period rather than immediately, so the\n * pointer can travel from the cell onto the tooltip (SC 1.4.13 Hoverable).\n */\n #scheduleHide(): void {\n this.#cancelPendingHide();\n if (this.#hideDelay <= 0) {\n this.#hideTooltip();\n return;\n }\n this.#hideTimer = setTimeout(() => {\n this.#hideTimer = null;\n this.#hideTooltip();\n }, this.#hideDelay);\n }\n\n #cancelPendingHide(): void {\n if (this.#hideTimer !== null) {\n clearTimeout(this.#hideTimer);\n this.#hideTimer = null;\n }\n }\n\n /** Remove the CSS anchor-name from the previous cell, but only if it's still our tooltip anchor. */\n #clearAnchor(): void {\n if (this.#anchorCell) {\n if (this.#anchorCell.style.getPropertyValue('anchor-name') === '--tbw-tooltip-anchor') {\n this.#anchorCell.style.removeProperty('anchor-name');\n }\n if (this.#anchorCell.getAttribute('aria-describedby') === POPOVER_ID) {\n this.#anchorCell.removeAttribute('aria-describedby');\n }\n this.#anchorCell = null;\n }\n }\n\n /**\n * Decide whether the popover sits above the cell. Above is preferred so the\n * tooltip never covers the rows the pointer is heading toward; it drops below\n * only when the popover does not fit in the space above the cell.\n *\n * Placement is resolved here rather than left to `position-try-fallbacks` so\n * the arrow-direction class can never disagree with where the browser\n * actually painted the popover.\n */\n #prefersAbove(cell: HTMLElement): boolean {\n if (!this.#popoverEl) return false;\n const cellRect = cell.getBoundingClientRect();\n const spaceAbove = cellRect.top - ARROW_GAP_PX;\n const spaceBelow = window.innerHeight - cellRect.bottom - ARROW_GAP_PX;\n // Popover is already shown and filled, so its box is measurable.\n const height = this.#popoverEl.offsetHeight;\n return height <= spaceAbove || spaceAbove >= spaceBelow;\n }\n\n /**\n * Fallback positioning for browsers without CSS anchor support.\n * Places the popover above or below the cell using fixed coordinates.\n */\n #positionFallback(cell: HTMLElement): void {\n if (!this.#popoverEl) return;\n const cellRect = cell.getBoundingClientRect();\n const above = this.#prefersAbove(cell);\n\n this.#popoverEl.style.position = 'fixed';\n this.#popoverEl.style.left = `${cellRect.left}px`;\n\n if (above) {\n this.#popoverEl.style.top = '';\n this.#popoverEl.style.bottom = `${window.innerHeight - cellRect.top + ARROW_GAP_PX}px`;\n } else {\n this.#popoverEl.style.top = `${cellRect.bottom + ARROW_GAP_PX}px`;\n this.#popoverEl.style.bottom = '';\n }\n this.#popoverEl.classList.toggle('tbw-tooltip-above', above);\n }\n // #endregion\n\n // #region Event Delegation\n\n /** Bind delegated mouseover/mouseout once. */\n #bindEvents(): void {\n if (this.#bound) return;\n const container = this.gridElement?.querySelector('.tbw-grid-root');\n if (!container) return;\n\n this.#bound = true;\n\n container.addEventListener('mouseover', (e: Event) => this.#onMouseOver(e as MouseEvent), {\n signal: this.disconnectSignal,\n });\n\n container.addEventListener('mouseout', (e: Event) => this.#onMouseOut(e as MouseEvent), {\n signal: this.disconnectSignal,\n });\n\n // SC 1.4.13 \"Dismissible\" — Escape must hide the tooltip without moving the\n // pointer. Bound on `document` because a hover-triggered tooltip can be\n // visible while focus sits entirely outside the grid, where the grid's own\n // key handling never runs. The event is deliberately NOT consumed: Escape\n // also cancels an in-progress edit, and swallowing it here would break that.\n document.addEventListener('keydown', (e: Event) => this.#onKeyDownGlobal(e as KeyboardEvent), {\n capture: true,\n signal: this.disconnectSignal,\n });\n\n // Keyboard navigation keeps DOM focus on the grid HOST (`tabindex=0`), so\n // keydown never travels through `.tbw-grid-root` — a child of the host.\n // Bind on the host, exactly where `setupRootEventDelegation` binds the\n // grid's own navigation handler.\n this.gridElement.addEventListener('keydown', (e: Event) => this.#onKeyDownGrid(e as KeyboardEvent), {\n signal: this.disconnectSignal,\n });\n }\n\n #onMouseOver(e: MouseEvent): void {\n const target = e.target as HTMLElement;\n if (!target?.closest) return;\n\n // Check for header cell\n const headerCell = target.closest('[part~=\"header-cell\"]') as HTMLElement | null;\n if (headerCell && this.#headerEnabled) {\n // Re-showing on every child boundary would re-measure (scrollWidth,\n // getBoundingClientRect) and re-anchor the popover, thrashing layout.\n if (!isSameCellCrossing(headerCell, e.relatedTarget)) {\n this.#showHeaderTooltip(headerCell);\n }\n return;\n }\n\n // Check for data cell — skip cells that already have a CSS anchor (e.g. overlay editors)\n // to avoid overwriting their anchor-name and breaking their positioning.\n const dataCell = target.closest('[data-row][data-col]') as HTMLElement | null;\n if (dataCell && this.#cellEnabled && !dataCell.style.getPropertyValue('anchor-name')) {\n if (isSameCellCrossing(dataCell, e.relatedTarget)) return;\n this.#showCellTooltip(dataCell);\n }\n }\n\n #onMouseOut(e: MouseEvent): void {\n const target = e.target as HTMLElement;\n if (!target?.closest) return;\n\n const cell = target.closest('[part~=\"header-cell\"], [data-row][data-col]') as HTMLElement | null;\n if (!cell) return;\n\n // Keep tooltip if pointer moved to a child still inside the same cell\n if (isSameCellCrossing(cell, e.relatedTarget)) return;\n\n // Grace period, not an immediate hide, so the pointer can reach the tooltip.\n this.#scheduleHide();\n }\n\n /** Escape dismisses a visible tooltip from anywhere on the page. */\n #onKeyDownGlobal(e: KeyboardEvent): void {\n if (e.key === 'Escape' && this.#isVisible) {\n this.#hideTooltip();\n }\n }\n\n /**\n * Retarget the tooltip after keyboard navigation moves the virtual cell\n * focus (SC 1.4.13 \"focus\" trigger). Only key-driven focus qualifies —\n * pointer interaction never reaches here, so mouse users are unaffected.\n */\n #onKeyDownGrid(e: KeyboardEvent): void {\n if (!this.#focusEnabled || !this.#cellEnabled) return;\n if (e.key === 'Escape') return;\n if (!NAVIGATION_KEYS.has(e.key)) {\n // Any other key (typing, Enter to edit) means the user has moved on.\n this.#hideTooltip();\n return;\n }\n // Cell focus is virtual and moves in the grid's own keydown handler, which\n // runs after this one — read the resulting `.cell-focus` on the next frame.\n if (this.#focusFrame !== null) cancelAnimationFrame(this.#focusFrame);\n this.#focusFrame = requestAnimationFrame(() => {\n this.#focusFrame = null;\n this.#syncTooltipToFocusedCell();\n });\n }\n\n /** Show (or clear) the tooltip for the cell that currently holds focus. */\n #syncTooltipToFocusedCell(): void {\n const cell = this.gridElement?.querySelector('.cell-focus') as HTMLElement | null;\n if (!cell || !cell.hasAttribute('data-row')) {\n this.#hideTooltip();\n return;\n }\n // Focus was clamped rather than moved (ArrowUp on the first row, ArrowDown\n // on the last, …) — the tooltip already describes this cell.\n if (cell === this.#anchorCell) return;\n // A foreign anchor means an overlay editor owns this cell's positioning, so\n // we cannot re-anchor here; drop the previous cell's tooltip rather than\n // leave it pointing somewhere focus has left.\n if (cell.style.getPropertyValue('anchor-name')) {\n this.#hideTooltip();\n return;\n }\n const before = this.#anchorCell;\n this.#showCellTooltip(cell);\n // No tooltip text for this cell — drop whatever the previous cell showed.\n if (this.#anchorCell === before) this.#hideTooltip();\n }\n // #endregion\n\n // #region Tooltip Resolution\n\n #showHeaderTooltip(headerCell: HTMLElement): void {\n const colIndex = parseInt(headerCell.getAttribute('data-col') ?? '-1', 10);\n if (colIndex < 0) return;\n\n const column = this.visibleColumns[colIndex];\n if (!column) return;\n\n const text = resolveHeaderTooltip(column, headerCell);\n if (text) {\n this.#showTooltip(headerCell, text);\n }\n }\n\n #showCellTooltip(cell: HTMLElement): void {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex < 0 || colIndex < 0) return;\n\n const column = this.visibleColumns[colIndex];\n if (!column) return;\n\n const row = this.rows[rowIndex];\n const value = row?.[column.field as keyof typeof row];\n\n const text = resolveCellTooltip(column, cell, row, value, this.grid);\n if (text) {\n this.#showTooltip(cell, text);\n }\n }\n // #endregion\n}\n// #endregion\n"],"mappings":"iYCoBA,SAAS,EAAc,GACrB,OAAO,EAAG,YAAc,EAAG,WAC7B,CAUA,SAAS,EAAmB,EAAmB,GAC7C,MAAM,EAAO,EACb,QAAS,GAAQ,EAAK,SAAS,EACjC,CA2DA,SAAS,IACP,MAAqD,mBAAvC,YAAY,WAAW,WACvC,CAQA,IAAM,EAAkB,IAAI,IAAI,CAC9B,UACA,YACA,YACA,aACA,OACA,MACA,SACA,WACA,QAUI,EAAa,sBAuDN,EAAb,cAAmC,EAAA,eACjC,KAAgB,UAChB,w9DAEA,gBAAmE,CACjE,gBAAiB,CACf,CAAE,SAAU,cAAe,MAAO,SAAU,YAAa,qCACzD,CAAE,SAAU,gBAAiB,MAAO,SAAU,YAAa,wCAE7D,YAAa,IAIf,GAAiC,KAGjC,GAAkC,KAGlC,IAAS,EAGT,GAAmD,KAGnD,GAA6B,KAG7B,KAAI,GACF,OAA8B,IAAvB,KAAK,OAAO,MACrB,CAGA,KAAI,GACF,OAA4B,IAArB,KAAK,OAAO,IACrB,CAGA,KAAI,GACF,OAA6B,IAAtB,KAAK,OAAO,KACrB,CAGA,KAAI,GACF,OAAO,KAAK,OAAO,WAzGO,GA0G5B,CAEA,MAAA,CAAgB,GACd,MAAM,OAAO,EACf,CAEA,MAAA,GACE,MAAK,IACoB,OAArB,MAAK,IACP,qBAAqB,MAAK,GAC1B,MAAK,EAAc,MAErB,MAAK,IACL,MAAK,GAAY,SACjB,MAAK,EAAa,KAClB,MAAK,GAAS,EACd,MAAM,QACR,CAEA,WAAA,GACE,MAAK,IACL,MAAK,GACP,CAKA,EAAA,GACE,GAAI,MAAK,EAAY,OACrB,MAAM,EAAK,SAAS,cAAc,OAClC,EAAG,UAAY,sBACf,EAAG,aAAa,UAAW,QAC3B,EAAG,GAAK,EACR,EAAG,aAAa,OAAQ,WAExB,EAAG,MAAM,SAAW,UACpB,EAAG,MAAM,OAAS,IAClB,SAAS,KAAK,YAAY,GAC1B,MAAK,EAAa,EAIlB,EAAG,iBAAiB,aAAA,IAAoB,MAAK,IAAsB,CAAE,OAAQ,KAAK,mBAClF,EAAG,iBAAiB,aAAA,IAAoB,MAAK,IAAiB,CAAE,OAAQ,KAAK,kBAC/E,CAGA,EAAA,CAAa,EAAmB,GAC9B,GAAK,MAAK,EAAV,CAiBA,GAhBA,MAAK,IAGL,MAAK,IACL,EAAK,MAAM,YAAY,cAAe,wBACtC,EAAK,aAAa,mBAAoB,GACtC,MAAK,EAAc,EAMnB,MAAK,EAAW,YAAc,EAC9B,MAAK,EAAW,UAAU,OAAO,wBAAyB,EAAK,SAAS,OAGpE,IACF,IACE,MAAK,EAAW,aAClB,CAAA,MAEA,CAjMkB,oBAAR,MAAmE,IAA5C,IAAI,WAAW,cAAe,UAqM/D,MAAK,EAAW,UAAU,OAAO,oBAAqB,MAAK,EAAc,IAEzE,MAAK,EAAkB,EA5BH,CA8BxB,CAGA,EAAA,GAEE,GADA,MAAK,IACD,MAAK,EAAY,CACnB,GAAI,IACF,IACE,MAAK,EAAW,aAClB,CAAA,MAEA,CAEF,MAAK,EAAW,UAAU,OAAO,oBACnC,CACA,MAAK,GACP,CAGA,KAAI,GACF,OAA4B,OAArB,MAAK,CACd,CAMA,EAAA,GACE,MAAK,IACD,MAAK,GAAc,EACrB,MAAK,IAGP,MAAK,EAAa,WAAA,KAChB,MAAK,EAAa,KAClB,MAAK,KACJ,MAAK,EACV,CAEA,EAAA,GAC0B,OAApB,MAAK,IACP,aAAa,MAAK,GAClB,MAAK,EAAa,KAEtB,CAGA,EAAA,GACM,MAAK,IACwD,yBAA3D,MAAK,EAAY,MAAM,iBAAiB,gBAC1C,MAAK,EAAY,MAAM,eAAe,eAEpC,MAAK,EAAY,aAAa,sBAAwB,GACxD,MAAK,EAAY,gBAAgB,oBAEnC,MAAK,EAAc,KAEvB,CAWA,EAAA,CAAc,GACZ,IAAK,MAAK,EAAY,OAAO,EAC7B,MAAM,EAAW,EAAK,wBAChB,EAAa,EAAS,IA5PX,GA6PX,EAAa,OAAO,YAAc,EAAS,OA7PhC,GAgQjB,OADe,MAAK,EAAW,cACd,GAAc,GAAc,CAC/C,CAMA,EAAA,CAAkB,GAChB,IAAK,MAAK,EAAY,OACtB,MAAM,EAAW,EAAK,wBAChB,EAAQ,MAAK,EAAc,GAEjC,MAAK,EAAW,MAAM,SAAW,QACjC,MAAK,EAAW,MAAM,KAAO,GAAG,EAAS,SAErC,GACF,MAAK,EAAW,MAAM,IAAM,GAC5B,MAAK,EAAW,MAAM,OAAY,OAAO,YAAc,EAAS,IAjRjD,GAiRgB,OAE/B,MAAK,EAAW,MAAM,IAAM,GAAG,EAAS,OAnRzB,OAoRf,MAAK,EAAW,MAAM,OAAS,IAEjC,MAAK,EAAW,UAAU,OAAO,oBAAqB,EACxD,CAMA,EAAA,GACE,GAAI,MAAK,EAAQ,OACjB,MAAM,EAAY,KAAK,aAAa,cAAc,kBAC7C,IAEL,MAAK,GAAS,EAEd,EAAU,iBAAiB,YAAc,GAAa,MAAK,EAAa,GAAkB,CACxF,OAAQ,KAAK,mBAGf,EAAU,iBAAiB,WAAa,GAAa,MAAK,EAAY,GAAkB,CACtF,OAAQ,KAAK,mBAQf,SAAS,iBAAiB,UAAY,GAAa,MAAK,EAAiB,GAAqB,CAC5F,SAAS,EACT,OAAQ,KAAK,mBAOf,KAAK,YAAY,iBAAiB,UAAY,GAAa,MAAK,EAAe,GAAqB,CAClG,OAAQ,KAAK,mBAEjB,CAEA,EAAA,CAAa,GACX,MAAM,EAAS,EAAE,OACjB,IAAK,GAAQ,QAAS,OAGtB,MAAM,EAAa,EAAO,QAAQ,yBAClC,GAAI,GAAc,MAAK,EAMrB,YAHK,EAAmB,EAAY,EAAE,gBACpC,MAAK,EAAmB,IAO5B,MAAM,EAAW,EAAO,QAAQ,wBAChC,GAAI,GAAY,MAAK,IAAiB,EAAS,MAAM,iBAAiB,eAAgB,CACpF,GAAI,EAAmB,EAAU,EAAE,eAAgB,OACnD,MAAK,EAAiB,EACxB,CACF,CAEA,EAAA,CAAY,GACV,MAAM,EAAS,EAAE,OACjB,IAAK,GAAQ,QAAS,OAEtB,MAAM,EAAO,EAAO,QAAQ,+CACvB,IAGD,EAAmB,EAAM,EAAE,gBAG/B,MAAK,IACP,CAGA,EAAA,CAAiB,GACD,WAAV,EAAE,KAAoB,MAAK,GAC7B,MAAK,GAET,CAOA,EAAA,CAAe,GACR,MAAK,GAAkB,MAAK,GACnB,WAAV,EAAE,MACD,EAAgB,IAAI,EAAE,MAOF,OAArB,MAAK,GAAsB,qBAAqB,MAAK,GACzD,MAAK,EAAc,sBAAA,KACjB,MAAK,EAAc,KACnB,MAAK,OARL,MAAK,IAUT,CAGA,EAAA,GACE,MAAM,EAAO,KAAK,aAAa,cAAc,eAC7C,IAAK,IAAS,EAAK,aAAa,YAE9B,YADA,MAAK,IAKP,GAAI,IAAS,MAAK,EAAa,OAI/B,GAAI,EAAK,MAAM,iBAAiB,eAE9B,YADA,MAAK,IAGP,MAAM,EAAS,MAAK,EACpB,MAAK,EAAiB,GAElB,MAAK,IAAgB,GAAQ,MAAK,GACxC,CAKA,EAAA,CAAmB,GACjB,MAAM,EAAW,SAAS,EAAW,aAAa,aAAe,KAAM,IACvE,GAAI,EAAW,EAAG,OAElB,MAAM,EAAS,KAAK,eAAe,GACnC,IAAK,EAAQ,OAEb,MAAM,EAtdV,SAA8B,EAAsB,GAClD,MAAM,EAAO,EAAO,cAEpB,IAAa,IAAT,EAAgB,OAAO,KAC3B,GAAoB,iBAAT,EAAmB,OAAO,EACrC,GAAoB,mBAAT,EAKT,OAAO,EAAK,CAHV,SACA,MAAO,EAAO,QAAU,EAAO,QAOnC,MAAM,EADY,EAAW,cAAc,qBACf,EAE5B,OAAI,EAAc,IACT,EAAO,aAAa,QAGtB,IACT,CAgciB,CAAqB,EAAQ,GACtC,GACF,MAAK,EAAa,EAAY,EAElC,CAEA,EAAA,CAAiB,GACf,MAAM,EAAW,SAAS,EAAK,aAAa,aAAe,KAAM,IAC3D,EAAW,SAAS,EAAK,aAAa,aAAe,KAAM,IACjE,GAAI,EAAW,GAAK,EAAW,EAAG,OAElC,MAAM,EAAS,KAAK,eAAe,GACnC,IAAK,EAAQ,OAEb,MAAM,EAAM,KAAK,KAAK,GAChB,EAAQ,IAAM,EAAO,OAErB,EAngBV,SACE,EACA,EACA,EACA,EACA,GAEA,MAAM,EAAO,EAAO,YAEpB,OAAa,IAAT,EAAuB,KACP,iBAAT,EAA0B,EACjB,mBAAT,EAEF,EAAK,CADqB,QAAO,MAAK,SAAQ,MAAO,EAAO,MAAa,SAK9E,EAAc,IACT,EAAK,aAAa,QAGpB,IACT,CA6eiB,CAAmB,EAAQ,EAAM,EAAK,EAAO,KAAK,MAC3D,GACF,MAAK,EAAa,EAAM,EAE5B"}
1
+ {"version":3,"file":"tooltip.umd.js","names":[],"sources":["../../../../../libs/grid/src/lib/plugins/tooltip/tooltip.css?inline","../../../../../libs/grid/src/lib/plugins/tooltip/tooltip-plugin.ts"],"sourcesContent":["@layer tbw-plugins {\n .tbw-tooltip-popover {\n /* Reset browser popover defaults — overflow:visible set inline for UA specificity */\n margin: 0;\n padding: var(--tbw-tooltip-padding, var(--tbw-spacing-sm, 0.375rem) var(--tbw-spacing-md, 0.5rem));\n background: var(--tbw-tooltip-bg, light-dark(#333338, #484850));\n color: var(--tbw-tooltip-fg, light-dark(#f5f5f5, #f0f0f0));\n border: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n border-radius: var(--tbw-tooltip-radius, var(--tbw-border-radius, 0.25rem));\n box-shadow: none;\n filter: var(--tbw-tooltip-shadow, drop-shadow(0 4px 4px rgba(0, 0, 0, 0.45)));\n font-family: var(--tbw-font-family, system-ui, sans-serif);\n font-size: var(--tbw-tooltip-font-size, var(--tbw-font-size-sm, 0.9285em));\n max-width: var(--tbw-tooltip-max-width, 300px);\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n /* WCAG 2.2 SC 1.4.13 \"Hoverable\": the pointer must be able to rest on the\n tooltip without dismissing it, so long or truncated content can be read,\n magnified, and selected. The plugin's grace-period hide covers the gap\n between the cell and the popover. */\n pointer-events: auto;\n user-select: text;\n z-index: 10001;\n }\n\n /* Multi-line content (text contains `\\n`): preserve author line breaks\n verbatim and let the popover grow horizontally instead of wrapping\n mid-line. `max-width` is lifted so long lines aren't broken. */\n .tbw-tooltip-popover.tbw-tooltip-multiline {\n white-space: pre;\n max-width: none;\n word-wrap: normal;\n overflow-wrap: normal;\n }\n\n /* Arrow — a 14×14 box rotated 45° to form a diamond, with its center\n pinned to the popover edge. Without `clip-path` the diamond's inner\n half sits *inside* the popover, painting `background: inherit` over\n the first ~7px of text content. The clip-path keeps only the\n pre-rotation triangle whose three corners rotate to the outside of\n the popover (the visible \"V\"), so the inner half is removed before\n it can overlap the content. clip-path is applied in the element's\n own (pre-transform) coordinate space per the CSS Transforms spec,\n which is why the polygon is expressed in the unrotated square. */\n .tbw-tooltip-popover::after {\n content: '';\n position: absolute;\n width: var(--tbw-tooltip-arrow-size, 14px);\n height: var(--tbw-tooltip-arrow-size, 14px);\n left: var(--tbw-tooltip-arrow-offset, 16px);\n background: inherit;\n pointer-events: none;\n }\n\n /* Arrow below-anchor default: diamond points up from the top edge.\n Pre-rotation: top-left, top-right, bottom-left → after rotate(45deg)\n these become the top, right, and left points of the diamond, i.e.\n the upper \"V\" that pokes above the popover. The bottom-right corner\n (which would land inside the popover) is clipped away. */\n .tbw-tooltip-popover:not(.tbw-tooltip-above)::after {\n top: calc(var(--tbw-tooltip-arrow-size, 14px) / -2);\n transform: rotate(45deg);\n border-top: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n border-left: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n clip-path: polygon(0 0, 100% 0, 0 100%);\n }\n\n /* Above-anchor: diamond points down from the bottom edge. Mirror of\n the below-anchor clip — keep the corners (top-right, bottom-right,\n bottom-left) that rotate to the right, bottom, and left points of\n the diamond, i.e. the lower \"Λ\" that pokes below the popover. */\n .tbw-tooltip-popover.tbw-tooltip-above::after {\n bottom: calc(var(--tbw-tooltip-arrow-size, 14px) / -2);\n transform: rotate(45deg);\n border-bottom: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n border-right: 1px solid var(--tbw-tooltip-border, light-dark(#222226, #6a6a72));\n clip-path: polygon(100% 0, 100% 100%, 0 100%);\n }\n\n /* CSS Anchor Positioning — modern browsers.\n Placement is chosen in JS (`#prefersAbove`) rather than by\n `position-try-fallbacks`: the browser's flip is invisible to script, so the\n arrow-direction class could disagree with where the popover was painted —\n most visibly when re-hovering while a previous tooltip is still open. */\n @supports (anchor-name: --test) {\n .tbw-tooltip-popover {\n position-anchor: --tbw-tooltip-anchor;\n top: calc(anchor(bottom) + 11px);\n bottom: auto;\n left: anchor(left);\n }\n\n .tbw-tooltip-popover.tbw-tooltip-above {\n top: auto;\n bottom: calc(anchor(top) + 11px);\n }\n }\n}\n","/**\n * Tooltip Plugin\n *\n * Shows styled popover tooltips on header and data cells when text\n * overflows (ellipsis). Uses the Popover API (`popover=\"hint\"`) with\n * CSS anchor positioning for consistent, themed placement.\n *\n * Supports per-column overrides via `cellTooltip` and `headerTooltip`\n * on column config.\n */\n\nimport type { GridElement, PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { CellRenderContext, ColumnConfig, HeaderLabelContext } from '../../core/types';\nimport tooltipStyles from './tooltip.css?inline';\nimport type { TooltipConfig } from './types';\n\n// #region Helpers\n\n/** Check if an element's text content overflows its visible width. */\nfunction isOverflowing(el: HTMLElement): boolean {\n return el.scrollWidth > el.clientWidth;\n}\n\n/**\n * True when a delegated pointer event merely crossed a child boundary *inside*\n * `cell` rather than entering or leaving it.\n *\n * `mouseover`/`mouseout` fire on every element boundary and bubble, so a\n * delegated handler sees one event per child. `relatedTarget` is what recovers\n * `mouseenter`/`mouseleave` semantics from that stream.\n */\nfunction isSameCellCrossing(cell: HTMLElement, related: EventTarget | null): boolean {\n const node = related as Node | null;\n return !!node && cell.contains(node);\n}\n\n/**\n * Resolve the tooltip text for a cell.\n * Returns the text to show, or `null` to suppress.\n */\nfunction resolveCellTooltip(\n column: ColumnConfig,\n cell: HTMLElement,\n row: unknown,\n value: unknown,\n grid?: GridElement,\n): string | null {\n const spec = column.cellTooltip;\n\n if (spec === false) return null;\n if (typeof spec === 'string') return spec;\n if (typeof spec === 'function') {\n const ctx: CellRenderContext = { value, row, column, field: column.field, grid: grid as any };\n return spec(ctx);\n }\n\n // Default: show textContent only when overflowing\n if (isOverflowing(cell)) {\n return cell.textContent?.trim() || null;\n }\n\n return null;\n}\n\n/**\n * Resolve the tooltip text for a header cell.\n * Returns the text to show, or `null` to suppress.\n */\nfunction resolveHeaderTooltip(column: ColumnConfig, headerCell: HTMLElement): string | null {\n const spec = column.headerTooltip;\n\n if (spec === false) return null;\n if (typeof spec === 'string') return spec;\n if (typeof spec === 'function') {\n const ctx: HeaderLabelContext = {\n column,\n value: column.header ?? column.field,\n };\n return spec(ctx);\n }\n\n // Default: show header text only when overflowing\n const labelSpan = headerCell.querySelector('span:first-child') as HTMLElement | null;\n const target = labelSpan ?? headerCell;\n\n if (isOverflowing(target)) {\n return target.textContent?.trim() || null;\n }\n\n return null;\n}\n\n/** Runtime check — happy-dom and older browsers may lack Popover API. */\nfunction supportsPopover(): boolean {\n return typeof HTMLElement.prototype?.showPopover === 'function';\n}\n\n/** Runtime check for CSS anchor positioning. */\nfunction supportsAnchor(): boolean {\n return typeof CSS !== 'undefined' && CSS.supports?.('anchor-name', '--test') === true;\n}\n\n/** Keys that move the virtual cell focus and should therefore re-target the tooltip. */\nconst NAVIGATION_KEYS = new Set([\n 'ArrowUp',\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n 'Home',\n 'End',\n 'PageUp',\n 'PageDown',\n 'Tab',\n]);\n\n/** Default grace period (ms) allowing the pointer to travel onto the tooltip. */\nconst DEFAULT_HIDE_DELAY_MS = 120;\n\n/** Gap (px) between the anchor cell and the popover, leaving room for the arrow. */\nconst ARROW_GAP_PX = 11;\n\n/** `id` of the shared popover, referenced by `aria-describedby` on the anchor. */\nconst POPOVER_ID = 'tbw-tooltip-popover';\n// #endregion\n\n// #region TooltipPlugin\n/**\n * Tooltip Plugin for tbw-grid\n *\n * Shows styled popover tooltips when header or cell text overflows its\n * container. Uses the Popover API with CSS anchor positioning for\n * consistent themed appearance across light and dark modes.\n *\n * ## Installation\n *\n * ```ts\n * import { TooltipPlugin } from '@toolbox-web/grid/plugins/tooltip';\n * ```\n *\n * @example Default — auto-tooltip on overflow\n * ```ts\n * grid.gridConfig = {\n * plugins: [new TooltipPlugin()],\n * };\n * ```\n *\n * @example Header-only tooltips\n * ```ts\n * grid.gridConfig = {\n * plugins: [new TooltipPlugin({ cell: false })],\n * };\n * ```\n *\n * @example Per-column overrides\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', cellTooltip: (ctx) => `${ctx.row.first} ${ctx.row.last}` },\n * { field: 'actions', cellTooltip: false },\n * { field: 'revenue', headerTooltip: 'Total revenue in USD (before tax)' },\n * ],\n * plugins: [new TooltipPlugin()],\n * };\n * ```\n *\n * ## Accessibility\n *\n * Conforms to WCAG 2.2 SC 1.4.13 (Content on Hover or Focus) out of the box:\n * the tooltip is **dismissible** with `Escape`, **hoverable** (the pointer can\n * rest on it to read or copy the text), **persistent** (never hidden on a\n * timer), and appears on **keyboard focus** as cell focus moves. Pointer\n * interaction never triggers the focus path, so mouse behaviour is unchanged.\n * See {@link TooltipConfig.focus} and {@link TooltipConfig.hideDelay} to tune it.\n *\n * @category Plugins\n * @since 1.28.0\n */\nexport class TooltipPlugin extends BaseGridPlugin<TooltipConfig> {\n readonly name = 'tooltip';\n override readonly styles = tooltipStyles;\n\n static override readonly manifest: PluginManifest<TooltipConfig> = {\n ownedProperties: [\n { property: 'cellTooltip', level: 'column', description: 'the \"cellTooltip\" column property' },\n { property: 'headerTooltip', level: 'column', description: 'the \"headerTooltip\" column property' },\n ],\n configRules: [],\n };\n\n /** The shared popover element for all tooltips. */\n #popoverEl: HTMLElement | null = null;\n\n /** The cell currently acting as CSS anchor. */\n #anchorCell: HTMLElement | null = null;\n\n /** Whether delegated listeners are bound. */\n #bound = false;\n\n /** Pending grace-period hide, cancelled when the pointer enters the tooltip. */\n #hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** Pending focus-driven retarget, so rapid arrow-key repeats coalesce. */\n #focusFrame: number | null = null;\n\n /** Whether header tooltips are enabled globally. */\n get #headerEnabled(): boolean {\n return this.config.header !== false;\n }\n\n /** Whether cell tooltips are enabled globally. */\n get #cellEnabled(): boolean {\n return this.config.cell !== false;\n }\n\n /** Whether keyboard focus shows a tooltip (WCAG 2.2 SC 1.4.13). */\n get #focusEnabled(): boolean {\n return this.config.focus !== false;\n }\n\n /** Grace period before hiding, allowing the pointer to reach the tooltip. */\n get #hideDelay(): number {\n return this.config.hideDelay ?? DEFAULT_HIDE_DELAY_MS;\n }\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n }\n\n override detach(): void {\n this.#cancelPendingHide();\n if (this.#focusFrame !== null) {\n cancelAnimationFrame(this.#focusFrame);\n this.#focusFrame = null;\n }\n this.#hideTooltip();\n this.#popoverEl?.remove();\n this.#popoverEl = null;\n this.#bound = false;\n super.detach();\n }\n\n override afterRender(): void {\n this.#ensurePopover();\n this.#bindEvents();\n }\n\n // #region Popover Lifecycle\n\n /** Create the shared popover element (once). */\n #ensurePopover(): void {\n if (this.#popoverEl) return;\n const el = document.createElement('div');\n el.className = 'tbw-tooltip-popover';\n el.setAttribute('popover', 'hint');\n el.id = POPOVER_ID;\n el.setAttribute('role', 'tooltip');\n // Override UA popover defaults that @layer CSS cannot beat\n el.style.overflow = 'visible';\n el.style.margin = '0';\n document.body.appendChild(el);\n this.#popoverEl = el;\n\n // SC 1.4.13 \"Hoverable\" — the pointer must be able to rest on the tooltip\n // without dismissing it, so its content can be read, magnified, or copied.\n el.addEventListener('mouseenter', () => this.#cancelPendingHide(), { signal: this.disconnectSignal });\n el.addEventListener('mouseleave', () => this.#scheduleHide(), { signal: this.disconnectSignal });\n // Scrolling slides the anchored popover under a stationary cursor; without this the\n // popover (in <body>) would swallow every wheel tick and the grid would stop scrolling.\n el.addEventListener('wheel', (e) => this.#forwardWheel(e), { passive: false, signal: this.disconnectSignal });\n }\n\n /** Dismiss the tooltip and re-dispatch the wheel on its anchor cell so the grid scrolls. */\n #forwardWheel(e: WheelEvent): void {\n const target = this.#anchorCell;\n this.#hideTooltip();\n if (!target?.isConnected) return;\n const forwarded = new WheelEvent('wheel', {\n deltaX: e.deltaX,\n deltaY: e.deltaY,\n deltaZ: e.deltaZ,\n deltaMode: e.deltaMode,\n clientX: e.clientX,\n clientY: e.clientY,\n shiftKey: e.shiftKey,\n ctrlKey: e.ctrlKey,\n altKey: e.altKey,\n metaKey: e.metaKey,\n bubbles: true,\n cancelable: true,\n composed: true,\n });\n if (!target.dispatchEvent(forwarded)) e.preventDefault();\n }\n\n /** Show the popover anchored to `cell` with the given `text`. */\n #showTooltip(cell: HTMLElement, text: string): void {\n if (!this.#popoverEl) return;\n this.#cancelPendingHide();\n\n // Move the CSS anchor to the hovered cell\n this.#clearAnchor();\n cell.style.setProperty('anchor-name', '--tbw-tooltip-anchor');\n cell.setAttribute('aria-describedby', POPOVER_ID);\n this.#anchorCell = cell;\n\n // Set content (always textContent — safe, no XSS).\n // Multi-line content uses `white-space: pre` so author-supplied line\n // breaks survive verbatim and the popover grows to fit; single-line\n // content keeps `pre-wrap` so long values wrap inside `max-width`.\n this.#popoverEl.textContent = text;\n this.#popoverEl.classList.toggle('tbw-tooltip-multiline', text.includes('\\n'));\n\n // Show via Popover API\n if (supportsPopover()) {\n try {\n this.#popoverEl.showPopover();\n } catch {\n /* already shown */\n }\n }\n\n if (supportsAnchor()) {\n this.#popoverEl.classList.toggle('tbw-tooltip-above', this.#prefersAbove(cell));\n } else {\n this.#positionFallback(cell);\n }\n }\n\n /** Hide the popover and clear the anchor reference. */\n #hideTooltip(): void {\n this.#cancelPendingHide();\n if (this.#popoverEl) {\n if (supportsPopover()) {\n try {\n this.#popoverEl.hidePopover();\n } catch {\n /* already hidden */\n }\n }\n this.#popoverEl.classList.remove('tbw-tooltip-above');\n }\n this.#clearAnchor();\n }\n\n /** Whether the popover is currently anchored to a cell (i.e. visible). */\n get #isVisible(): boolean {\n return this.#anchorCell !== null;\n }\n\n /**\n * Hide after the configured grace period rather than immediately, so the\n * pointer can travel from the cell onto the tooltip (SC 1.4.13 Hoverable).\n */\n #scheduleHide(): void {\n this.#cancelPendingHide();\n if (this.#hideDelay <= 0) {\n this.#hideTooltip();\n return;\n }\n this.#hideTimer = setTimeout(() => {\n this.#hideTimer = null;\n this.#hideTooltip();\n }, this.#hideDelay);\n }\n\n #cancelPendingHide(): void {\n if (this.#hideTimer !== null) {\n clearTimeout(this.#hideTimer);\n this.#hideTimer = null;\n }\n }\n\n /** Remove the CSS anchor-name from the previous cell, but only if it's still our tooltip anchor. */\n #clearAnchor(): void {\n if (this.#anchorCell) {\n if (this.#anchorCell.style.getPropertyValue('anchor-name') === '--tbw-tooltip-anchor') {\n this.#anchorCell.style.removeProperty('anchor-name');\n }\n if (this.#anchorCell.getAttribute('aria-describedby') === POPOVER_ID) {\n this.#anchorCell.removeAttribute('aria-describedby');\n }\n this.#anchorCell = null;\n }\n }\n\n /**\n * Decide whether the popover sits above the cell. Above is preferred so the\n * tooltip never covers the rows the pointer is heading toward; it drops below\n * only when the popover does not fit in the space above the cell.\n *\n * Placement is resolved here rather than left to `position-try-fallbacks` so\n * the arrow-direction class can never disagree with where the browser\n * actually painted the popover.\n */\n #prefersAbove(cell: HTMLElement): boolean {\n if (!this.#popoverEl) return false;\n const cellRect = cell.getBoundingClientRect();\n const spaceAbove = cellRect.top - ARROW_GAP_PX;\n const spaceBelow = window.innerHeight - cellRect.bottom - ARROW_GAP_PX;\n // Popover is already shown and filled, so its box is measurable.\n const height = this.#popoverEl.offsetHeight;\n return height <= spaceAbove || spaceAbove >= spaceBelow;\n }\n\n /**\n * Fallback positioning for browsers without CSS anchor support.\n * Places the popover above or below the cell using fixed coordinates.\n */\n #positionFallback(cell: HTMLElement): void {\n if (!this.#popoverEl) return;\n const cellRect = cell.getBoundingClientRect();\n const above = this.#prefersAbove(cell);\n\n this.#popoverEl.style.position = 'fixed';\n this.#popoverEl.style.left = `${cellRect.left}px`;\n\n if (above) {\n this.#popoverEl.style.top = '';\n this.#popoverEl.style.bottom = `${window.innerHeight - cellRect.top + ARROW_GAP_PX}px`;\n } else {\n this.#popoverEl.style.top = `${cellRect.bottom + ARROW_GAP_PX}px`;\n this.#popoverEl.style.bottom = '';\n }\n this.#popoverEl.classList.toggle('tbw-tooltip-above', above);\n }\n // #endregion\n\n // #region Event Delegation\n\n /** Bind delegated mouseover/mouseout once. */\n #bindEvents(): void {\n if (this.#bound) return;\n const container = this.gridElement?.querySelector('.tbw-grid-root');\n if (!container) return;\n\n this.#bound = true;\n\n container.addEventListener('mouseover', (e: Event) => this.#onMouseOver(e as MouseEvent), {\n signal: this.disconnectSignal,\n });\n\n container.addEventListener('mouseout', (e: Event) => this.#onMouseOut(e as MouseEvent), {\n signal: this.disconnectSignal,\n });\n\n // SC 1.4.13 \"Dismissible\" — Escape must hide the tooltip without moving the\n // pointer. Bound on `document` because a hover-triggered tooltip can be\n // visible while focus sits entirely outside the grid, where the grid's own\n // key handling never runs. The event is deliberately NOT consumed: Escape\n // also cancels an in-progress edit, and swallowing it here would break that.\n document.addEventListener('keydown', (e: Event) => this.#onKeyDownGlobal(e as KeyboardEvent), {\n capture: true,\n signal: this.disconnectSignal,\n });\n\n // Keyboard navigation keeps DOM focus on the grid HOST (`tabindex=0`), so\n // keydown never travels through `.tbw-grid-root` — a child of the host.\n // Bind on the host, exactly where `setupRootEventDelegation` binds the\n // grid's own navigation handler.\n this.gridElement.addEventListener('keydown', (e: Event) => this.#onKeyDownGrid(e as KeyboardEvent), {\n signal: this.disconnectSignal,\n });\n }\n\n #onMouseOver(e: MouseEvent): void {\n const target = e.target as HTMLElement;\n if (!target?.closest) return;\n\n // Check for header cell\n const headerCell = target.closest('[part~=\"header-cell\"]') as HTMLElement | null;\n if (headerCell && this.#headerEnabled) {\n // Re-showing on every child boundary would re-measure (scrollWidth,\n // getBoundingClientRect) and re-anchor the popover, thrashing layout.\n if (!isSameCellCrossing(headerCell, e.relatedTarget)) {\n this.#showHeaderTooltip(headerCell);\n }\n return;\n }\n\n // Check for data cell — skip cells that already have a CSS anchor (e.g. overlay editors)\n // to avoid overwriting their anchor-name and breaking their positioning.\n const dataCell = target.closest('[data-row][data-col]') as HTMLElement | null;\n if (dataCell && this.#cellEnabled && !dataCell.style.getPropertyValue('anchor-name')) {\n if (isSameCellCrossing(dataCell, e.relatedTarget)) return;\n this.#showCellTooltip(dataCell);\n }\n }\n\n #onMouseOut(e: MouseEvent): void {\n const target = e.target as HTMLElement;\n if (!target?.closest) return;\n\n const cell = target.closest('[part~=\"header-cell\"], [data-row][data-col]') as HTMLElement | null;\n if (!cell) return;\n\n // Keep tooltip if pointer moved to a child still inside the same cell\n if (isSameCellCrossing(cell, e.relatedTarget)) return;\n\n // Grace period, not an immediate hide, so the pointer can reach the tooltip.\n this.#scheduleHide();\n }\n\n /** Escape dismisses a visible tooltip from anywhere on the page. */\n #onKeyDownGlobal(e: KeyboardEvent): void {\n if (e.key === 'Escape' && this.#isVisible) {\n this.#hideTooltip();\n }\n }\n\n /**\n * Retarget the tooltip after keyboard navigation moves the virtual cell\n * focus (SC 1.4.13 \"focus\" trigger). Only key-driven focus qualifies —\n * pointer interaction never reaches here, so mouse users are unaffected.\n */\n #onKeyDownGrid(e: KeyboardEvent): void {\n if (!this.#focusEnabled || !this.#cellEnabled) return;\n if (e.key === 'Escape') return;\n if (!NAVIGATION_KEYS.has(e.key)) {\n // Any other key (typing, Enter to edit) means the user has moved on.\n this.#hideTooltip();\n return;\n }\n // Cell focus is virtual and moves in the grid's own keydown handler, which\n // runs after this one — read the resulting `.cell-focus` on the next frame.\n if (this.#focusFrame !== null) cancelAnimationFrame(this.#focusFrame);\n this.#focusFrame = requestAnimationFrame(() => {\n this.#focusFrame = null;\n this.#syncTooltipToFocusedCell();\n });\n }\n\n /** Show (or clear) the tooltip for the cell that currently holds focus. */\n #syncTooltipToFocusedCell(): void {\n const cell = this.gridElement?.querySelector('.cell-focus') as HTMLElement | null;\n if (!cell || !cell.hasAttribute('data-row')) {\n this.#hideTooltip();\n return;\n }\n // Focus was clamped rather than moved (ArrowUp on the first row, ArrowDown\n // on the last, …) — the tooltip already describes this cell.\n if (cell === this.#anchorCell) return;\n // A foreign anchor means an overlay editor owns this cell's positioning, so\n // we cannot re-anchor here; drop the previous cell's tooltip rather than\n // leave it pointing somewhere focus has left.\n if (cell.style.getPropertyValue('anchor-name')) {\n this.#hideTooltip();\n return;\n }\n const before = this.#anchorCell;\n this.#showCellTooltip(cell);\n // No tooltip text for this cell — drop whatever the previous cell showed.\n if (this.#anchorCell === before) this.#hideTooltip();\n }\n // #endregion\n\n // #region Tooltip Resolution\n\n #showHeaderTooltip(headerCell: HTMLElement): void {\n const colIndex = parseInt(headerCell.getAttribute('data-col') ?? '-1', 10);\n if (colIndex < 0) return;\n\n const column = this.visibleColumns[colIndex];\n if (!column) return;\n\n const text = resolveHeaderTooltip(column, headerCell);\n if (text) {\n this.#showTooltip(headerCell, text);\n }\n }\n\n #showCellTooltip(cell: HTMLElement): void {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex < 0 || colIndex < 0) return;\n\n const column = this.visibleColumns[colIndex];\n if (!column) return;\n\n const row = this.rows[rowIndex];\n const value = row?.[column.field as keyof typeof row];\n\n const text = resolveCellTooltip(column, cell, row, value, this.grid);\n if (text) {\n this.#showTooltip(cell, text);\n }\n }\n // #endregion\n}\n// #endregion\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/constants"),require("../../core/internal/loading"),require("../../core/internal/sanitize"),require("../../core/internal/sorting"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/constants","../../core/internal/loading","../../core/internal/sanitize","../../core/internal/sorting","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_tree={},e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,r,i,n,s){function a(e,t,r){return void 0!==e.id?String(e.id):r?`${r}-${t}`:String(t)}function o(e,t){const r=new Set(e);return r.has(t)?r.delete(t):r.add(t),r}function d(e,t,r=null,i=0){const n=t.childrenField??"children",s=new Set;for(let o=0;o<e.length;o++){const l=e[o],h=a(l,o,r),c=l[n];if(Array.isArray(c)&&c.length>0){s.add(h);const e=d(c,t,h,i+1);for(const t of e)s.add(t)}}return s}function l(e,t,r,i=null,n=0){const s=r.childrenField??"children";for(let o=0;o<e.length;o++){const d=e[o],h=a(d,o,i);if(h===t)return[h];const c=d[s];if(Array.isArray(c)&&c.length>0){const e=l(c,t,r,h,n+1);if(e)return[h,...e]}}return null}function h(e,t,r,i){const n=l(e,t,r);if(!n)return i;const s=new Set(i);for(let a=0;a<n.length-1;a++)s.add(n[a]);return s}function c(e,t){const r=Math.min(t,e.length-1);if(r<0)return 0;let i=r;for(;i>0&&e[i].depth>0;)i--;let n=0;for(let s=0;s<=i;s++)0===e[s].depth&&n++;return n-1}function u(e){let t=0;for(const r of e)0===r.depth&&t++;return t}function p(e,t="children",r){if(!Array.isArray(e)||0===e.length)return!1;for(const i of e){if(!i)continue;const e=i[t];if(Array.isArray(e)&&e.length>0)return!0;if(null!=e&&!Array.isArray(e)&&e)return!0;if(r?.(i))return!0}return!1}Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var g=class extends s.BaseGridPlugin{static manifest={modifiesRowStructure:!0,hookPriority:{processRows:10},incompatibleWith:[{name:"groupingRows",reason:"Both plugins transform the entire row model. TreePlugin flattens nested hierarchies while GroupingRowsPlugin groups flat rows with synthetic headers. Use one approach per grid."},{name:"pivot",reason:"PivotPlugin replaces the entire row and column structure with aggregated pivot data. Tree hierarchy cannot coexist with pivot aggregation."}],events:[{type:"tree-expand",description:"Emitted when tree expansion state changes (toggle, expand all, collapse all). Broadcast to both DOM consumers and plugin bus."},{type:"tree-load-start",description:"Emitted when lazy child loading starts for a node whose children are not yet loaded."},{type:"tree-load-end",description:"Emitted when lazy child loading completes and children have been merged into the parent row."},{type:"tree-load-error",description:"Emitted when lazy child loading fails. The node leaves its loading state and can be retried."}],queries:[{type:"canMoveRow",description:"Returns false for rows with children (parent nodes cannot be reordered)"},{type:"datasource:viewport-mapping",description:"Translates flat viewport row indices to top-level node indices for ServerSide pagination."}]};static dependencies=[{name:"multiSort",required:!1,reason:"Queries sort model for coordinated tree sorting"},{name:"serverSide",required:!1,reason:"Consumes datasource events for lazy-loaded tree data"}];name="tree";styles='@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{justify-content:flex-start;align-items:center;padding:0;display:flex;border-inline-end:none!important}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1/3}tbw-grid .tree-cell-wrapper{vertical-align:middle;align-items:center;padding-inline-start:calc(var(--tbw-tree-depth,0) * var(--tbw-tree-indent-width,var(--tbw-tree-toggle-size,1.25em)));display:inline-flex;overflow:visible}tbw-grid .tree-expander{box-sizing:border-box;justify-content:flex-start;align-items:center;width:100%;height:100%;display:flex}tbw-grid .tree-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;width:var(--tbw-tree-toggle-size,1.25em);height:var(--tbw-tree-toggle-size,1.25em);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex}tbw-grid .tree-toggle:hover{color:var(--tbw-tree-accent,var(--tbw-color-accent))}tbw-grid .tree-toggle{position:relative}tbw-grid .tree-toggle:after{content:"";inline-size:var(--tbw-touch-target-min,24px);block-size:var(--tbw-touch-target-min,24px);position:absolute;inset-block-start:50%;inset-inline-start:50%;translate:-50% -50%}tbw-grid .tree-spacer{width:var(--tbw-tree-toggle-size,1.25em);height:var(--tbw-tree-toggle-size,1.25em);flex-shrink:0;display:inline-block}@media (pointer:coarse){tbw-grid .tree-toggle,tbw-grid .tree-spacer{min-width:var(--tbw-touch-target-min,24px);min-height:var(--tbw-touch-target-min,24px)}}tbw-grid .tree-loading{--tbw-spinner-size:var(--tbw-tree-toggle-size,1.25em);flex-shrink:0}tbw-grid .data-grid-row.tbw-tree-slide-in{animation:tbw-tree-slide-in var(--tbw-animation-duration,.2s) var(--tbw-animation-easing,ease-out) forwards}tbw-grid .data-grid-row.tbw-tree-fade-in{animation:tbw-tree-fade-in var(--tbw-animation-duration,.2s) var(--tbw-animation-easing,ease-out) forwards}@keyframes tbw-tree-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}@keyframes tbw-tree-fade-in{0%{opacity:0}to{opacity:1}}}';get defaultConfig(){return{childrenField:"children",autoDetect:!0,defaultExpanded:!1,indentWidth:20,showExpandIcons:!0,animation:"slide"}}expandedKeys=new Set;initialExpansionDone=!1;flattenedRows=[];rowKeyMap=new Map;previousVisibleKeys=new Set;keysToAnimate=new Set;sortState=null;loadingKeys=new Set;loadedKeys=new Set;#e=new Map;#t=new WeakMap;#r=new WeakMap;originalTreeColumnRenderer;wrappedTreeColumnField;#i=!1;#n=!1;detach(){(this.gridElement?.querySelector(".rows-body"))?.setAttribute("role","grid"),this.expandedKeys.clear(),this.initialExpansionDone=!1,this.flattenedRows=[],this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.sortState=null,this.loadingKeys.clear(),this.loadedKeys.clear();for(const e of this.#e.values())e.abort();this.#e.clear(),this.originalTreeColumnRenderer=void 0,this.wrappedTreeColumnField=void 0,this.#i=!1}handleQuery(e){if("canMoveRow"===e.type){const t=e.context,r=this.config.childrenField??"children",i=t?.[r];if(Array.isArray(i)&&i.length>0)return!1}if("datasource:viewport-mapping"===e.type){const{viewportStart:t,viewportEnd:r}=e.context;if(0===this.flattenedRows.length)return;return{startNode:c(this.flattenedRows,t),endNode:c(this.flattenedRows,r)+1,totalLoadedNodes:u(this.flattenedRows)}}}attach(e){super.attach(e),this.on("datasource:data",e=>{const t=e;t.claimed||(t.claimed=!0)}),this.on("datasource:children",e=>{const t=e;if("tree"!==t.context?.source)return;t.claimed=!0;const r=t.context.parentNode;if(r){const e=this.#s(r);this.#a(r,t.rows??[]),this.loadingKeys.delete(e),this.broadcast("tree-load-end",{key:e,row:r,depth:this.rowKeyMap.get(e)?.depth??0,childCount:t.rows?.length??0}),this.requestRender()}}),this.on("datasource:error",e=>{const t=e;if("tree"!==t.context?.source)return;const r=t.context.parentNode;if(!r)return;const i=this.#s(r);this.loadingKeys.delete(i)&&(this.broadcast("tree-load-error",{key:i,row:r,depth:this.rowKeyMap.get(i)?.depth??0,error:t.error}),this.requestRender())})}get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detect(e){if(!this.config.autoDetect)return!1;const t=e,r=this.config.childrenField??function(e){if(!Array.isArray(e)||0===e.length)return null;const t=["children","items","nodes","subRows","nested"];for(const r of e)if(r&&"object"==typeof r)for(const e of t){const t=r[e];if(Array.isArray(t)&&t.length>0)return e}return null}(t)??"children";return p(t,r)}processRows(e){const t=this.config.childrenField??"children",r=e;if(0===r.length||!p(r,t,this.config.hasChildren))return this.flattenedRows=[],this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),this.#o(),[...e];const i=this.resolveEffectiveSortState();this.config.defaultExpanded&&!this.initialExpansionDone&&(this.expandedKeys=d(r,this.config),this.initialExpansionDone=!0),this.flattenedRows=this.#d(r,this.expandedKeys,i,null,0),this.#r=new WeakMap,this.rowKeyMap.clear(),this.keysToAnimate.clear();const n=new Set;for(const s of this.flattenedRows)this.rowKeyMap.set(s.key,s),this.#r.set(s.data,s),n.add(s.key),!this.previousVisibleKeys.has(s.key)&&s.depth>0&&this.keysToAnimate.add(s.key);return this.previousVisibleKeys=n,this.#o(),this.flattenedRows.map(e=>e.data)}#o(){this.#n||(this.#n=!0,queueMicrotask(()=>{this.#n=!1,this.grid&&this.#i!==this.flattenedRows.length>0&&this.requestColumnsRender()}))}#l(e,t,r){if(void 0!==e.id){const t=String(e.id);return this.#t.set(e,t),t}const i=this.#t.get(e);if(void 0!==i)return i;const n=r?`${r}-${t}`:String(t);return this.#t.set(e,n),n}#d(e,t,r,i,n){const s=this.config.childrenField??"children";for(let l=0;l<e.length;l++)this.#l(e[l],l,i);const a=r?this.#h(e,r.field,r.direction):e,o=[],d=this.config.hasChildren;for(let l=0;l<a.length;l++){const e=a[l],h=this.#l(e,l,i),c=e[s],u=Array.isArray(c)&&c.length>0,p=u||(d?d(e):null!=c&&!Array.isArray(c)&&!!c),g=t.has(h);o.push({key:h,data:e,depth:n,hasChildren:p,isExpanded:g,parentKey:i,posInSet:l+1,setSize:a.length}),u&&g&&o.push(...this.#d(c,t,r,h,n+1))}return o}#h(e,t,r){const i=this.grid,s=i?._columns??[],a={field:t,direction:r},o=[...e],d=(i?.effectiveConfig?.sortHandler??n.builtInSort)(o,a,s);return d&&"function"==typeof d.then?(d.catch(()=>{}),(0,n.builtInSort)(o,a,s)):d}#s(e){return this.#t.get(e)??String(e.id??"?")}#a(e,t){e[this.config.childrenField??"children"]=t,this.loadedKeys.add(this.#s(e))}#c(e,t){if(this.loadedKeys.has(t))return!1;const r=e[this.config.childrenField??"children"];if(Array.isArray(r)&&r.length>0)return!1;const i=this.config.hasChildren;return i?i(e):null!=r&&!Array.isArray(r)&&!!r}requestLazyChildren(e){const{key:t,depth:r,data:i}=e;if(this.loadingKeys.has(t))return;if(!this.#c(i,t))return;const n=this.queryBoolean("datasource:is-active"),{loadChildren:s}=this.config;(n||s)&&(this.loadingKeys.add(t),this.broadcast("tree-load-start",{key:t,row:i,depth:r}),n?this.grid.query("datasource:fetch-children",{context:{source:"tree",parentNode:i,nodePath:[t]}}):this.#u(t,i,r))}#u(e,t,r){const i=this.config.loadChildren,n=new AbortController;this.#e.set(e,n);const s=()=>!n.signal.aborted&&(this.#e.delete(e),this.loadingKeys.delete(e),!0),a=i=>{if(!s())return;const n=i??[];this.#a(t,n),this.broadcast("tree-load-end",{key:e,row:t,depth:r,childCount:n.length}),this.requestRender()},o=i=>{s()&&(this.broadcast("tree-load-error",{key:e,row:t,depth:r,error:i}),this.requestRender())};let d;try{d=i({row:t,key:e,depth:r,signal:n.signal})}catch(l){return void o(l)}if(d&&"function"==typeof d.subscribe){let e=!1,t=null;const r=()=>{e=!0,t?.unsubscribe(),t=null},i=d.subscribe({next:e=>{a(e),r()},error:e=>{o(e),r()}});return e?void i.unsubscribe():(t=i,void n.signal.addEventListener("abort",r,{once:!0}))}Promise.resolve(d).then(a,o)}resolveEffectiveSortState(){const e=this.grid?.query?.("sort:get-model",null);if(Array.isArray(e)&&e.length>0){const t=e[0];if(Array.isArray(t)&&t.length>0)return{field:t[0].field,direction:"desc"===t[0].direction?-1:1}}return this.sortState}processColumns(e){if(0===this.flattenedRows.length){const t=[...e];if(this.#i){const e=t.findIndex(e=>e.field===this.wrappedTreeColumnField);e>=0&&(t[e]={...t[e],viewRenderer:this.originalTreeColumnRenderer})}return this.#i=!1,this.originalTreeColumnRenderer=void 0,this.wrappedTreeColumnField=void 0,t}const n=[...e];if(0===n.length)return n;const{treeColumn:s}=this.config;let a=0;if(s){const e=n.findIndex(e=>e.field===s);e>=0&&(a=e)}const o=n[a],d=o.field;this.wrappedTreeColumnField!==d&&(this.originalTreeColumnRenderer=o.viewRenderer,this.wrappedTreeColumnField=d);const l=this.originalTreeColumnRenderer,h=()=>this.config,c=this.setIcon.bind(this);return n[a]={...o,viewRenderer:e=>{const{row:n,value:s}=e,{showExpandIcons:a=!0,indentWidth:o}=h(),d=this.#r.get(n),u=d?.depth??0,p=document.createElement("span");if(p.className="tree-cell-wrapper",p.style.setProperty("--tbw-tree-depth",String(u)),void 0!==o&&p.style.setProperty("--tbw-tree-indent-width",`${o}px`),a)if(d&&this.loadingKeys.has(d.key)){const e=(0,r.createDefaultSpinner)("small");e.classList.add("tree-loading"),p.appendChild(e)}else if(d&&d.hasChildren){const e=document.createElement("span");e.className=`${t.GridClasses.TREE_TOGGLE}${d.isExpanded?` ${t.GridClasses.EXPANDED}`:""}`,c(e,d.isExpanded?"collapse":"expand"),e.setAttribute("data-tree-key",d.key),p.appendChild(e)}else{const e=document.createElement("span");e.className="tree-spacer",p.appendChild(e)}const g=document.createElement("span");if(g.className="tree-content",l){const t=l(e);t instanceof Node?g.appendChild(t):"string"==typeof t&&(0,i.setSanitizedHTML)(g,t)}else g.textContent=null!=s?String(s):"";return p.appendChild(g),p}},this.#i=!0,n}onCellClick(e){const r=e.originalEvent?.target;if(!r?.classList.contains(t.GridClasses.TREE_TOGGLE))return!1;const i=r.getAttribute("data-tree-key");if(!i)return!1;const n=this.rowKeyMap.get(i);return!!n&&(this.expandedKeys=o(this.expandedKeys,i),this.expandedKeys.has(i)&&this.requestLazyChildren(n),this.broadcast("tree-expand",{key:i,row:n.data,expanded:this.expandedKeys.has(i),depth:n.depth,expandedKeys:[...this.expandedKeys]}),this.requestRender(),!0)}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusRow,r=this.flattenedRows[t];return r?.hasChildren?(e.preventDefault(),this.expandedKeys=o(this.expandedKeys,r.key),this.expandedKeys.has(r.key)&&this.requestLazyChildren(r),this.broadcast("tree-expand",{key:r.key,row:r.data,expanded:this.expandedKeys.has(r.key),depth:r.depth,expandedKeys:[...this.expandedKeys]}),this.requestRenderWithFocus(),!0):void 0}onHeaderClick(e){if(0===this.flattenedRows.length||!e.column.sortable)return!1;const t=this.grid?.query?.("sort:get-model",null);if(Array.isArray(t)&&t.length>0)return!1;const{field:r}=e.column;this.sortState&&this.sortState.field===r?1===this.sortState.direction?this.sortState={field:r,direction:-1}:this.sortState=null:this.sortState={field:r,direction:1};const i=this.grid;return void 0!==i._sortState&&(i._sortState=this.sortState?{...this.sortState}:null),this.broadcast("sort-change",{field:r,direction:this.sortState?.direction??0}),this.requestRender(),!0}afterRender(){const e=this.gridElement?.querySelector(".rows-body");e&&"treegrid"!==e.getAttribute("role")&&e.setAttribute("role","treegrid");const t=this.gridElement?.querySelector(".rows");if(!t)return;const r=this.animationStyle,i=!1!==r&&this.keysToAnimate.size>0,n="fade"===r?"tbw-tree-fade-in":"tbw-tree-slide-in";for(const s of t.querySelectorAll(".data-grid-row")){const e=s.querySelector(".cell[data-row]"),t=e?parseInt(e.getAttribute("data-row")??"-1",10):-1,r=this.flattenedRows[t];r&&(s.setAttribute("aria-level",String(r.depth+1)),s.setAttribute("aria-setsize",String(r.setSize)),s.setAttribute("aria-posinset",String(r.posInSet)),r.hasChildren?s.setAttribute("aria-expanded",String(r.isExpanded)):s.hasAttribute("aria-expanded")&&s.removeAttribute("aria-expanded"),s.classList.toggle("tbw-row-expanded",r.hasChildren&&r.isExpanded),this.loadingKeys.has(r.key)?s.setAttribute("aria-busy","true"):s.hasAttribute("aria-busy")&&s.removeAttribute("aria-busy"),i&&r.key&&this.keysToAnimate.has(r.key)&&(s.classList.add(n),s.addEventListener("animationend",()=>s.classList.remove(n),{once:!0})))}this.keysToAnimate.clear()}expand(e){this.expandedKeys.add(e);const t=this.rowKeyMap.get(e);t&&this.requestLazyChildren(t),this.requestRender()}collapse(e){this.expandedKeys.delete(e),this.requestRender()}toggle(e){this.expandedKeys=o(this.expandedKeys,e);const t=this.rowKeyMap.get(e);t?(this.expandedKeys.has(e)&&this.requestLazyChildren(t),this.broadcast("tree-expand",{key:e,row:t.data,expanded:this.expandedKeys.has(e),depth:t.depth,expandedKeys:[...this.expandedKeys]})):this.emitPluginEvent("tree-expand",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}expandAll(){this.expandedKeys=d(this.rows,this.config),this.emitPluginEvent("tree-expand",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}collapseAll(){this.expandedKeys=new Set,this.emitPluginEvent("tree-expand",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}getExpandedKeys(){return[...this.expandedKeys]}getFlattenedRows(){return[...this.flattenedRows]}getRowMeta(e){return this.#r.get(e)}getRowByKey(e){return this.rowKeyMap.get(e)?.data}expandToKey(e){this.expandedKeys=h(this.rows,e,this.config,this.expandedKeys),this.requestRender()}};e.TreePlugin=g});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("../../core/constants"),require("../../core/internal/loading"),require("../../core/internal/sanitize"),require("../../core/internal/sorting"),require("../../core/plugin/base-plugin")):"function"==typeof define&&define.amd?define(["exports","../../core/constants","../../core/internal/loading","../../core/internal/sanitize","../../core/internal/sorting","../../core/plugin/base-plugin"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).TbwGridPlugin_tree={},e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid,e.TbwGrid)}(this,function(e,t,r,i,n,s){function o(e,t,r=new WeakMap){const i=[];for(const h of["structure","transform"])for(const r of t)r.hierarchyStage===h&&r.beginHierarchy(e)&&i.push(r);if(0===i.length)return[...e];const n=[],s=(e,t,r)=>{const n={parent:t?t.row:null,parentKey:t?.key??null,depth:r};for(const s of i)e=s.processSiblings(e,n);return e},o=(e,t)=>{const i=e.length;for(let a=0;a<i;a++){const d=e[a];n.push(d.row),"object"==typeof d.row&&null!==d.row&&r.set(d.row,{level:t+1,setSize:i,posInSet:a+1});const l=d.children;d.expanded&&l&&o(d.shaped?l:s(l,d,t+1),t+1)}},a=new Array(e.length);for(let h=0;h<e.length;h++)a[h]={row:e[h]};o(s(a,null,0),0);const d=e=>"object"==typeof e&&null!==e?r.get(e):void 0;let l=!1;for(const h of i)h.endHierarchy(n,d)&&(l=!0);if(!l)for(const h of n)"object"==typeof h&&null!==h&&r.delete(h);return n}function a(e,t,r){const i=String(r);e.getAttribute(t)!==i&&e.setAttribute(t,i)}function d(e){return"function"==typeof e.processSiblings}function l(e){return e.constructor.manifest?.hookPriority?.processRows??0}Object.defineProperty(e,Symbol.toStringTag,{value:"Module"});var h=class extends s.BaseGridPlugin{static manifest={modifiesRowStructure:!0};name="hierarchy";#e=new WeakMap;detach(){this.gridElement?.querySelector(".rows-body")?.setAttribute("role","grid"),this.#e=new WeakMap}process(e,t){const r=this.#t();return r.includes(t)?r[r.length-1]!==t?e:(this.#e=new WeakMap,o(e,r,this.#e)):(this.#e=new WeakMap,o(e,[t],this.#e))}getRowMeta(e){return"object"==typeof e&&null!==e?this.#e.get(e):void 0}afterRender(){this.#r()}onScrollRender(){this.#r()}#t(){return(this.grid?._pluginManager?.getPlugins?.()??[]).filter(d).sort((e,t)=>l(e)-l(t))}#r(){if(0===this.#t().length)return;const e=this.gridElement?.querySelector(".rows");if(!e)return;let t=!1;for(const n of e.querySelectorAll(".data-grid-row")){const e=this.getRowMeta(n.__rowDataRef);e?(t=!0,a(n,"aria-level",e.level),a(n,"aria-setsize",e.setSize),a(n,"aria-posinset",e.posInSet)):(n.removeAttribute("aria-level"),n.removeAttribute("aria-setsize"),n.removeAttribute("aria-posinset"))}const r=t?"treegrid":"grid",i=this.gridElement?.querySelector(".rows-body");i&&i.getAttribute("role")!==r&&i.setAttribute("role",r)}};function c(e,t,r){return void 0!==e.id?String(e.id):r?`${r}-${t}`:String(t)}function u(e,t){const r=new Set(e);return r.has(t)?r.delete(t):r.add(t),r}function p(e,t,r=null,i=0){const n=t.childrenField??"children",s=new Set;for(let o=0;o<e.length;o++){const a=e[o],d=c(a,o,r),l=a[n];if(Array.isArray(l)&&l.length>0){s.add(d);const e=p(l,t,d,i+1);for(const t of e)s.add(t)}}return s}function g(e,t,r,i=null,n=0){const s=r.childrenField??"children";for(let o=0;o<e.length;o++){const a=e[o],d=c(a,o,i);if(d===t)return[d];const l=a[s];if(Array.isArray(l)&&l.length>0){const e=g(l,t,r,d,n+1);if(e)return[d,...e]}}return null}function y(e,t,r,i){const n=g(e,t,r);if(!n)return i;const s=new Set(i);for(let o=0;o<n.length-1;o++)s.add(n[o]);return s}function f(e,t="children",r){if(!Array.isArray(e)||0===e.length)return!1;for(const i of e){if(!i)continue;const e=i[t];if(Array.isArray(e)&&e.length>0)return!0;if(null!=e&&!Array.isArray(e)&&e)return!0;if(r?.(i))return!0}return!1}var w=class extends s.BaseGridPlugin{static manifest={modifiesRowStructure:!0,hookPriority:{processRows:10},incompatibleWith:[{name:"pivot",reason:"PivotPlugin replaces the entire row and column structure with aggregated pivot data. Tree hierarchy cannot coexist with pivot aggregation."}],events:[{type:"tree-expand",description:"Emitted when tree expansion state changes (toggle, expand all, collapse all). Broadcast to both DOM consumers and plugin bus."},{type:"tree-load-start",description:"Emitted when lazy child loading starts for a node whose children are not yet loaded."},{type:"tree-load-end",description:"Emitted when lazy child loading completes and children have been merged into the parent row."},{type:"tree-load-error",description:"Emitted when lazy child loading fails. The node leaves its loading state and can be retried."}],queries:[{type:"canMoveRow",description:"Returns false for rows with children (parent nodes cannot be reordered)"},{type:"datasource:viewport-mapping",description:"Translates flat viewport row indices to top-level node indices for ServerSide pagination."}]};static dependencies=[{name:"hierarchy",required:!0,reason:"Builds the hierarchical row model",provide:()=>new h},{name:"multiSort",required:!1,reason:"Queries sort model for coordinated tree sorting"},{name:"serverSide",required:!1,reason:"Consumes datasource events for lazy-loaded tree data"}];name="tree";hierarchyStage="structure";styles='@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{justify-content:flex-start;align-items:center;padding:0;display:flex;border-inline-end:none!important}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1/3}tbw-grid .tree-cell-wrapper{vertical-align:middle;align-items:center;padding-inline-start:calc(var(--tbw-tree-depth,0) * var(--tbw-tree-indent-width,var(--tbw-tree-toggle-size,1.25em)));display:inline-flex;overflow:visible}tbw-grid .tree-expander{box-sizing:border-box;justify-content:flex-start;align-items:center;width:100%;height:100%;display:flex}tbw-grid .tree-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;width:var(--tbw-tree-toggle-size,1.25em);height:var(--tbw-tree-toggle-size,1.25em);flex-shrink:0;justify-content:center;align-items:center;display:inline-flex}tbw-grid .tree-toggle:hover{color:var(--tbw-tree-accent,var(--tbw-color-accent))}tbw-grid .tree-toggle{position:relative}tbw-grid .tree-toggle:after{content:"";inline-size:var(--tbw-touch-target-min,24px);block-size:var(--tbw-touch-target-min,24px);position:absolute;inset-block-start:50%;inset-inline-start:50%;translate:-50% -50%}tbw-grid .tree-spacer{width:var(--tbw-tree-toggle-size,1.25em);height:var(--tbw-tree-toggle-size,1.25em);flex-shrink:0;display:inline-block}@media (pointer:coarse){tbw-grid .tree-toggle,tbw-grid .tree-spacer{min-width:var(--tbw-touch-target-min,24px);min-height:var(--tbw-touch-target-min,24px)}}tbw-grid .tree-loading{--tbw-spinner-size:var(--tbw-tree-toggle-size,1.25em);flex-shrink:0}tbw-grid .data-grid-row.tbw-tree-slide-in{animation:tbw-tree-slide-in var(--tbw-animation-duration,.2s) var(--tbw-animation-easing,ease-out) forwards}tbw-grid .data-grid-row.tbw-tree-fade-in{animation:tbw-tree-fade-in var(--tbw-animation-duration,.2s) var(--tbw-animation-easing,ease-out) forwards}@keyframes tbw-tree-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}@keyframes tbw-tree-fade-in{0%{opacity:0}to{opacity:1}}}';get defaultConfig(){return{childrenField:"children",autoDetect:!0,defaultExpanded:!1,indentWidth:20,showExpandIcons:!0,animation:"slide"}}expandedKeys=new Set;initialExpansionDone=!1;flattenedRows=[];rowKeyMap=new Map;previousVisibleKeys=new Set;keysToAnimate=new Set;#i=[];#n=0;sortState=null;#s=null;loadingKeys=new Set;loadedKeys=new Set;#o=new Map;#a=new WeakMap;#d=new WeakMap;originalTreeColumnRenderer;wrappedTreeColumnField;#l=!1;#h=!1;detach(){this.expandedKeys.clear(),this.initialExpansionDone=!1,this.flattenedRows=[],this.#i=[],this.#n=0,this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),this.keysToAnimate.clear(),this.sortState=null,this.#s=null,this.loadingKeys.clear(),this.loadedKeys.clear();for(const e of this.#o.values())e.abort();this.#o.clear(),this.originalTreeColumnRenderer=void 0,this.wrappedTreeColumnField=void 0,this.#l=!1}handleQuery(e){if("canMoveRow"===e.type){const t=e.context,r=this.config.childrenField??"children",i=t?.[r];if(Array.isArray(i)&&i.length>0)return!1}if("datasource:viewport-mapping"===e.type){const{viewportStart:t,viewportEnd:r}=e.context;if(0===this.flattenedRows.length)return;return function(e,t,r,i){const n=e.length-1,s=t=>Math.max(0,e[Math.max(0,Math.min(t,n))]??0);return{startNode:s(r),endNode:s(i)+1,totalLoadedNodes:t}}(this.#i,this.#n,t,r)}}attach(e){super.attach(e),this.on("datasource:data",e=>{const t=e;t.claimed||(t.claimed=!0)}),this.on("datasource:children",e=>{const t=e;if("tree"!==t.context?.source)return;t.claimed=!0;const r=t.context.parentNode;if(r){const e=this.#c(r);this.#u(r,t.rows??[]),this.loadingKeys.delete(e),this.broadcast("tree-load-end",{key:e,row:r,depth:this.rowKeyMap.get(e)?.depth??0,childCount:t.rows?.length??0}),this.requestRender()}}),this.on("datasource:error",e=>{const t=e;if("tree"!==t.context?.source)return;const r=t.context.parentNode;if(!r)return;const i=this.#c(r);this.loadingKeys.delete(i)&&(this.broadcast("tree-load-error",{key:i,row:r,depth:this.rowKeyMap.get(i)?.depth??0,error:t.error}),this.requestRender())})}get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}detect(e){if(!this.config.autoDetect)return!1;const t=e,r=this.config.childrenField??function(e){if(!Array.isArray(e)||0===e.length)return null;const t=["children","items","nodes","subRows","nested"];for(const r of e)if(r&&"object"==typeof r)for(const e of t){const t=r[e];if(Array.isArray(t)&&t.length>0)return e}return null}(t)??"children";return f(t,r)}processRows(e){const t=this.grid?.getPluginByName?.("hierarchy");return t?t.process(e,this):o(e,[this])}beginHierarchy(e){const t=e;return 0!==t.length&&f(t,this.config.childrenField??"children",this.config.hasChildren)?(this.#s=this.resolveEffectiveSortState(),this.config.defaultExpanded&&!this.initialExpansionDone&&(this.expandedKeys=p(t,this.config),this.initialExpansionDone=!0),this.#d=new WeakMap,!0):(this.flattenedRows=[],this.#i=[],this.#n=0,this.rowKeyMap.clear(),this.previousVisibleKeys.clear(),this.#p(),!1)}processSiblings(e,t){const{parentKey:r,depth:i}=t,n=this.config.childrenField??"children",s=this.config.hasChildren;for(let d=0;d<e.length;d++)this.#g(e[d].row,d,r);const o=this.#s,a=o?this.#y(e,o.field,o.direction):e;for(let d=0;d<a.length;d++){const e=a[d],t=e.row,o=this.#g(t,d,r),l=t[n],h=Array.isArray(l)&&l.length>0,c=h||(s?s(t):null!=l&&!Array.isArray(l)&&!!l),u=this.expandedKeys.has(o);this.#d.set(t,{key:o,data:t,depth:i,hasChildren:c,isExpanded:u,parentKey:r,posInSet:d+1,setSize:a.length}),e.key=o,h&&u&&(e.expanded=!0,e.children=l.map(e=>({row:e})))}return a}endHierarchy(e,t){const r=this.#d;this.#d=new WeakMap,this.flattenedRows=[],this.rowKeyMap.clear(),this.keysToAnimate.clear();const i=new Set,n=new Array(e.length);let s=-1;for(let o=0;o<e.length;o++){const a=e[o],d=r.get(a);if(null===d?.parentKey&&s++,n[o]=s,!d)continue;const l=t(a)?.level;void 0!==l&&(d.depth=l-1),this.flattenedRows.push(d),this.rowKeyMap.set(d.key,d),this.#d.set(a,d),i.add(d.key),!this.previousVisibleKeys.has(d.key)&&d.depth>0&&this.keysToAnimate.add(d.key)}return this.previousVisibleKeys=i,this.#i=n,this.#n=s+1,this.#p(),!0}#p(){this.#h||(this.#h=!0,queueMicrotask(()=>{this.#h=!1,this.grid&&this.#l!==this.flattenedRows.length>0&&this.requestColumnsRender()}))}#g(e,t,r){if(void 0!==e.id){const t=String(e.id);return this.#a.set(e,t),t}const i=this.#a.get(e);if(void 0!==i)return i;const n=r?`${r}-${t}`:String(t);return this.#a.set(e,n),n}#y(e,t,r){const i=new Map;for(const n of e)i.set(n.row,n);return this.#f(e.map(e=>e.row),t,r).map(e=>i.get(e)??{row:e})}#f(e,t,r){const i=this.grid,s=i?._columns??[],o={field:t,direction:r},a=[...e],d=(i?.effectiveConfig?.sortHandler??n.builtInSort)(a,o,s);return d&&"function"==typeof d.then?(d.catch(()=>{}),(0,n.builtInSort)(a,o,s)):d}#c(e){return this.#a.get(e)??String(e.id??"?")}#u(e,t){e[this.config.childrenField??"children"]=t,this.loadedKeys.add(this.#c(e))}#w(e,t){if(this.loadedKeys.has(t))return!1;const r=e[this.config.childrenField??"children"];if(Array.isArray(r)&&r.length>0)return!1;const i=this.config.hasChildren;return i?i(e):null!=r&&!Array.isArray(r)&&!!r}requestLazyChildren(e){const{key:t,depth:r,data:i}=e;if(this.loadingKeys.has(t))return;if(!this.#w(i,t))return;const n=this.queryBoolean("datasource:is-active"),{loadChildren:s}=this.config;(n||s)&&(this.loadingKeys.add(t),this.broadcast("tree-load-start",{key:t,row:i,depth:r}),n?this.grid.query("datasource:fetch-children",{context:{source:"tree",parentNode:i,nodePath:[t]}}):this.#m(t,i,r))}#m(e,t,r){const i=this.config.loadChildren,n=new AbortController;this.#o.set(e,n);const s=()=>!n.signal.aborted&&(this.#o.delete(e),this.loadingKeys.delete(e),!0),o=i=>{if(!s())return;const n=i??[];this.#u(t,n),this.broadcast("tree-load-end",{key:e,row:t,depth:r,childCount:n.length}),this.requestRender()},a=i=>{s()&&(this.broadcast("tree-load-error",{key:e,row:t,depth:r,error:i}),this.requestRender())};let d;try{d=i({row:t,key:e,depth:r,signal:n.signal})}catch(l){return void a(l)}if(d&&"function"==typeof d.subscribe){let e=!1,t=null;const r=()=>{e=!0,t?.unsubscribe(),t=null},i=d.subscribe({next:e=>{o(e),r()},error:e=>{a(e),r()}});return e?void i.unsubscribe():(t=i,void n.signal.addEventListener("abort",r,{once:!0}))}Promise.resolve(d).then(o,a)}resolveEffectiveSortState(){const e=this.grid?.query?.("sort:get-model",null);if(Array.isArray(e)&&e.length>0){const t=e[0];if(Array.isArray(t)&&t.length>0)return{field:t[0].field,direction:"desc"===t[0].direction?-1:1}}return this.sortState}processColumns(e){if(0===this.flattenedRows.length){const t=[...e];if(this.#l){const e=t.findIndex(e=>e.field===this.wrappedTreeColumnField);e>=0&&(t[e]={...t[e],viewRenderer:this.originalTreeColumnRenderer})}return this.#l=!1,this.originalTreeColumnRenderer=void 0,this.wrappedTreeColumnField=void 0,t}const n=[...e];if(0===n.length)return n;const{treeColumn:s}=this.config;let o=0;if(s){const e=n.findIndex(e=>e.field===s);e>=0&&(o=e)}const a=n[o],d=a.field;this.wrappedTreeColumnField!==d&&(this.originalTreeColumnRenderer=a.viewRenderer,this.wrappedTreeColumnField=d);const l=this.originalTreeColumnRenderer,h=()=>this.config,c=this.setIcon.bind(this);return n[o]={...a,viewRenderer:e=>{const{row:n,value:s}=e,{showExpandIcons:o=!0,indentWidth:a}=h(),d=this.#d.get(n),u=d?.depth??0,p=document.createElement("span");if(p.className="tree-cell-wrapper",p.style.setProperty("--tbw-tree-depth",String(u)),void 0!==a&&p.style.setProperty("--tbw-tree-indent-width",`${a}px`),o)if(d&&this.loadingKeys.has(d.key)){const e=(0,r.createDefaultSpinner)("small");e.classList.add("tree-loading"),p.appendChild(e)}else if(d&&d.hasChildren){const e=document.createElement("span");e.className=`${t.GridClasses.TREE_TOGGLE}${d.isExpanded?` ${t.GridClasses.EXPANDED}`:""}`,c(e,d.isExpanded?"collapse":"expand"),e.setAttribute("data-tree-key",d.key),p.appendChild(e)}else{const e=document.createElement("span");e.className="tree-spacer",p.appendChild(e)}const g=document.createElement("span");if(g.className="tree-content",l){const t=l(e);t instanceof Node?g.appendChild(t):"string"==typeof t&&(0,i.setSanitizedHTML)(g,t)}else g.textContent=null!=s?String(s):"";return p.appendChild(g),p}},this.#l=!0,n}onCellClick(e){const r=e.originalEvent?.target;if(!r?.classList.contains(t.GridClasses.TREE_TOGGLE))return!1;const i=r.getAttribute("data-tree-key");if(!i)return!1;const n=this.rowKeyMap.get(i);return!!n&&(this.expandedKeys=u(this.expandedKeys,i),this.expandedKeys.has(i)&&this.requestLazyChildren(n),this.broadcast("tree-expand",{key:i,row:n.data,expanded:this.expandedKeys.has(i),depth:n.depth,expandedKeys:[...this.expandedKeys]}),this.requestRender(),!0)}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusRow,r=this.#d.get(this.rows[t]);return r?.hasChildren?(e.preventDefault(),this.expandedKeys=u(this.expandedKeys,r.key),this.expandedKeys.has(r.key)&&this.requestLazyChildren(r),this.broadcast("tree-expand",{key:r.key,row:r.data,expanded:this.expandedKeys.has(r.key),depth:r.depth,expandedKeys:[...this.expandedKeys]}),this.requestRenderWithFocus(),!0):void 0}onHeaderClick(e){if(0===this.flattenedRows.length||!e.column.sortable)return!1;const t=this.grid?.query?.("sort:get-model",null);if(Array.isArray(t)&&t.length>0)return!1;const{field:r}=e.column;this.sortState&&this.sortState.field===r?1===this.sortState.direction?this.sortState={field:r,direction:-1}:this.sortState=null:this.sortState={field:r,direction:1};const i=this.grid;return void 0!==i._sortState&&(i._sortState=this.sortState?{...this.sortState}:null),this.broadcast("sort-change",{field:r,direction:this.sortState?.direction??0}),this.requestRender(),!0}afterRender(){const e=this.gridElement?.querySelector(".rows");if(!e)return;const t=this.animationStyle,r=!1!==t&&this.keysToAnimate.size>0,i="fade"===t?"tbw-tree-fade-in":"tbw-tree-slide-in";for(const n of e.querySelectorAll(".data-grid-row")){const e=this.#d.get(n.__rowDataRef);e&&(e.hasChildren?n.setAttribute("aria-expanded",String(e.isExpanded)):n.hasAttribute("aria-expanded")&&n.removeAttribute("aria-expanded"),n.classList.toggle("tbw-row-expanded",e.hasChildren&&e.isExpanded),this.loadingKeys.has(e.key)?n.setAttribute("aria-busy","true"):n.hasAttribute("aria-busy")&&n.removeAttribute("aria-busy"),r&&e.key&&this.keysToAnimate.has(e.key)&&(n.classList.add(i),n.addEventListener("animationend",()=>n.classList.remove(i),{once:!0})))}this.keysToAnimate.clear()}expand(e){this.expandedKeys.add(e);const t=this.rowKeyMap.get(e);t&&this.requestLazyChildren(t),this.requestRender()}collapse(e){this.expandedKeys.delete(e),this.requestRender()}toggle(e){this.expandedKeys=u(this.expandedKeys,e);const t=this.rowKeyMap.get(e);t?(this.expandedKeys.has(e)&&this.requestLazyChildren(t),this.broadcast("tree-expand",{key:e,row:t.data,expanded:this.expandedKeys.has(e),depth:t.depth,expandedKeys:[...this.expandedKeys]})):this.emitPluginEvent("tree-expand",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}expandAll(){this.expandedKeys=p(this.rows,this.config),this.emitPluginEvent("tree-expand",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}collapseAll(){this.expandedKeys=new Set,this.emitPluginEvent("tree-expand",{expandedKeys:[...this.expandedKeys]}),this.requestRender()}isExpanded(e){return this.expandedKeys.has(e)}getExpandedKeys(){return[...this.expandedKeys]}getFlattenedRows(){return[...this.flattenedRows]}getRowMeta(e){return this.#d.get(e)}getRowByKey(e){return this.rowKeyMap.get(e)?.data}expandToKey(e){this.expandedKeys=y(this.rows,e,this.config,this.expandedKeys),this.requestRender()}};e.TreePlugin=w});
2
2
  //# sourceMappingURL=tree.umd.js.map