@toolbox-web/grid 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.d.ts +1 -0
- package/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/core/internal/aria.d.ts +4 -0
- package/lib/core/types.d.ts +43 -0
- package/lib/features/sticky-rows.d.ts +9 -0
- package/lib/features/sticky-rows.js +2 -0
- package/lib/features/sticky-rows.js.map +1 -0
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +1 -1
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +15 -0
- package/lib/plugins/grouping-rows/index.js +2 -2
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder-columns/index.js.map +1 -1
- package/lib/plugins/reorder-rows/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-drag-drop/index.js.map +1 -1
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/sticky-rows/StickyRowsPlugin.d.ts +114 -0
- package/lib/plugins/sticky-rows/index.d.ts +7 -0
- package/lib/plugins/sticky-rows/index.js +2 -0
- package/lib/plugins/sticky-rows/index.js.map +1 -0
- package/lib/plugins/sticky-rows/types.d.ts +67 -0
- package/lib/plugins/tooltip/index.js.map +1 -1
- package/lib/plugins/tree/index.js +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/tree/types.d.ts +4 -0
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/sticky-rows.umd.js +2 -0
- package/umd/plugins/sticky-rows.umd.js.map +1 -0
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { BaseGridPlugin } from '../../core/plugin/base-plugin';
|
|
2
|
+
import { ScrollEvent } from '../../core/plugin/types';
|
|
3
|
+
import { StickyRowsConfig } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Sticky Rows plugin for `<tbw-grid>`.
|
|
6
|
+
*
|
|
7
|
+
* Marks specific rows as "sticky" so they pin below the grid header when their
|
|
8
|
+
* natural scroll position would take them off-screen. Behavior when multiple
|
|
9
|
+
* sticky rows would be stuck simultaneously is controlled by `mode`:
|
|
10
|
+
*
|
|
11
|
+
* - `'push'` (default) — only one stuck at a time; the next sticky row pushes
|
|
12
|
+
* the previous one out of view (iOS section-header behavior).
|
|
13
|
+
* - `'stack'` — sticky rows stack below the header up to `maxStacked`.
|
|
14
|
+
*
|
|
15
|
+
* The plugin renders **clones** of the real rows; the originals continue to
|
|
16
|
+
* exist in the data flow. Clones inherit the row's grid-template alignment so
|
|
17
|
+
* they line up perfectly with the column boundaries below.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import '@toolbox-web/grid/features/sticky-rows';
|
|
22
|
+
*
|
|
23
|
+
* grid.gridConfig = {
|
|
24
|
+
* features: { stickyRows: { isSticky: 'isSection', mode: 'stack' } },
|
|
25
|
+
* };
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @see {@link StickyRowsConfig} for all configuration options.
|
|
29
|
+
*
|
|
30
|
+
* @internal Extends BaseGridPlugin.
|
|
31
|
+
*/
|
|
32
|
+
export declare class StickyRowsPlugin extends BaseGridPlugin<StickyRowsConfig> {
|
|
33
|
+
/** @internal */
|
|
34
|
+
readonly name = "stickyRows";
|
|
35
|
+
/** @internal */
|
|
36
|
+
readonly styles: string;
|
|
37
|
+
/** @internal */
|
|
38
|
+
protected get defaultConfig(): Partial<StickyRowsConfig>;
|
|
39
|
+
/** Container element that holds the stuck-row clones. */
|
|
40
|
+
private container;
|
|
41
|
+
/** Indices in `grid.rows` whose rows are sticky, sorted ascending. */
|
|
42
|
+
private stickyIndices;
|
|
43
|
+
/** Cached clone DOM keyed by row index. Survives the row leaving the
|
|
44
|
+
* virtualization window so we can keep showing it while stuck. */
|
|
45
|
+
private cloneCache;
|
|
46
|
+
/** Currently displayed indices (in DOM order). Used to skip work when the
|
|
47
|
+
* set hasn't changed between scroll ticks. */
|
|
48
|
+
private displayedIndices;
|
|
49
|
+
/** Last applied push-mode translation, to skip writes on no-op ticks. */
|
|
50
|
+
private lastPushOffset;
|
|
51
|
+
/** @internal */
|
|
52
|
+
detach(): void;
|
|
53
|
+
/** @internal Recompute the sticky-index list and refresh display. */
|
|
54
|
+
afterRender(): void;
|
|
55
|
+
/** @internal Re-render clones after a scroll-driven row pool refresh. */
|
|
56
|
+
onScrollRender(): void;
|
|
57
|
+
/** @internal Update which clones show / how they're positioned. */
|
|
58
|
+
onScroll(event: ScrollEvent): void;
|
|
59
|
+
private resolvePredicate;
|
|
60
|
+
private recomputeStickyIndices;
|
|
61
|
+
private ensureContainer;
|
|
62
|
+
/** Pixel offset of the top of `index` from the start of the scroll area. */
|
|
63
|
+
private offsetOf;
|
|
64
|
+
/** Estimated height of `index` (used for push-mode displacement). */
|
|
65
|
+
private heightOf;
|
|
66
|
+
private getVirtualState;
|
|
67
|
+
private getCurrentScrollTop;
|
|
68
|
+
/**
|
|
69
|
+
* Compute the displayed sticky indices and any upward push offset, given
|
|
70
|
+
* the current scroll offset.
|
|
71
|
+
*
|
|
72
|
+
* - `'push'` mode: a row qualifies once its top has scrolled past the
|
|
73
|
+
* viewport top (strict `<`); only the highest qualifying index is
|
|
74
|
+
* shown. As the next sticky approaches from below, the stuck clone
|
|
75
|
+
* slides upward by `heightOfStuck - distance` (iOS section-header
|
|
76
|
+
* behavior).
|
|
77
|
+
* - `'stack'` mode: each subsequent sticky qualifies once its top
|
|
78
|
+
* reaches the bottom of the existing stack (`offsetOf(idx) < scrollTop
|
|
79
|
+
* + cumulativeHeightOfStuck`), so `— B —` latches when it meets
|
|
80
|
+
* `— A —`'s bottom — not after `— A —` covers it. When the stack is
|
|
81
|
+
* at `maxStacked` and the **live** next sticky row's top crosses the
|
|
82
|
+
* bottom of the stack (`distance <= 0`), the entire stack translates
|
|
83
|
+
* upward by `-distance`. Live rows above the stack (including the
|
|
84
|
+
* incoming sticky) scroll up naturally beneath the overlay; rows-
|
|
85
|
+
* viewport's `overflow: clip` hides anything above the top edge. When
|
|
86
|
+
* the oldest is fully off (pushOffset ≥ heightOf(oldest)), the next
|
|
87
|
+
* tick qualifies the new sticky and we slice to `last max` with
|
|
88
|
+
* transform reset — at that exact pixel the live new sticky is sitting
|
|
89
|
+
* precisely behind its slot in the new stack, producing a seamless
|
|
90
|
+
* swap with no duplicate row visible.
|
|
91
|
+
*
|
|
92
|
+
* Strict `<` keeps the qualification edge symmetric with push mode and
|
|
93
|
+
* avoids briefly rendering both a clone and the live row at the swap
|
|
94
|
+
* pixel.
|
|
95
|
+
*/
|
|
96
|
+
private computeDisplay;
|
|
97
|
+
/** Locate a rendered row in the viewport by its data index. */
|
|
98
|
+
private findRenderedRow;
|
|
99
|
+
/** Build (or refresh) a clone for `index`, using the live row when present. */
|
|
100
|
+
private buildClone;
|
|
101
|
+
/** Refresh cached clones for any displayed indices that are now in-window.
|
|
102
|
+
* Pulls fresh DOM so edits / re-renders are reflected when the row swings
|
|
103
|
+
* back into view. */
|
|
104
|
+
private refreshClonesInWindow;
|
|
105
|
+
/** Build clones for any sticky row currently rendered in the viewport so we
|
|
106
|
+
* have one ready when the row scrolls past. Without this, a sticky row
|
|
107
|
+
* that's never been displayed-as-stuck won't have a cache entry, and once
|
|
108
|
+
* the live row leaves the rendered window we have nothing to render. */
|
|
109
|
+
private primeCloneCache;
|
|
110
|
+
/** Reconcile the container DOM with the desired displayed indices. */
|
|
111
|
+
private refreshDisplay;
|
|
112
|
+
/** Find the next sticky index strictly greater than `current`. */
|
|
113
|
+
private findNextSticky;
|
|
114
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function t(t,e){return`[tbw-grid${t?`#${t}`:""}${e?`:${e}`:""}]`}function e(e,i,s,n){return`${t(s,n)} ${e}: ${i}\n\n → More info: ${function(t){return`https://toolboxjs.com/grid/errors#${t.toLowerCase()}`}(e)}`}const i="__otorp__|__retteGenifed__|__retteSenifed__|rotcurtsnoc|wodniw|sihTlabolg|labolg|ssecorp|noitcnuF|tropmi|lave|tcelfeR|yxorP|rorrE|stnemugra|tnemucod|noitacol|eikooc|egarotSlacol|egarotSnoisses|BDdexedni|hctef|tseuqeRpttHLMX|tekcoSbeW|rekroW|rekroWderahS|rekroWecivreS|renepo|tnerap|pot|semarf|fles".split("|").map(t=>t.split("").reverse().join(""));new RegExp(`__(proto|defineGetter|defineSetter)|${i.slice(3).join("|")}|this\\b`);const s=new Set("script|iframe|object|embed|form|input|button|textarea|select|link|meta|base|style|template|slot|portal|frame|frameset|applet|noscript|noembed|plaintext|xmp|listing".split("|")),n=/^on\w+$/i,r=new Set("href|src|action|formaction|data|srcdoc|xlink:href|poster|srcset".split("|")),o=/^\s*(javascript|vbscript|data|blob):/i;function c(t){if(!t||"string"!=typeof t)return"";if(-1===t.indexOf("<"))return t;const e=document.createElement("template");return e.innerHTML=t,function(t){const e=[],i=t.querySelectorAll("*");for(const c of i){const t=c.tagName.toLowerCase();if(s.has(t)){e.push(c);continue}if("svg"===t||"http://www.w3.org/2000/svg"===c.namespaceURI){if(Array.from(c.attributes).some(t=>n.test(t.name)||"href"===t.name||"xlink:href"===t.name)){e.push(c);continue}}const i=[];for(const e of c.attributes){const t=e.name.toLowerCase();n.test(t)?i.push(e.name):(r.has(t)&&o.test(e.value)||"style"===t&&/expression\s*\(|javascript:|behavior\s*:/i.test(e.value))&&i.push(e.name)}i.forEach(t=>c.removeAttribute(t))}e.forEach(t=>t.remove())}(e.content),e.innerHTML}const a={expand:"▶",collapse:"▼",sortAsc:"▲",sortDesc:"▼",sortNone:"⇅",submenuArrow:"▶",dragHandle:"⋮⋮",toolPanel:"☰",filter:"",filterActive:"",print:"🖨️"};class l{static dependencies;static manifest;aliases;version="undefined"!=typeof __GRID_VERSION__?__GRID_VERSION__:"dev";styles;cellRenderers;headerRenderers;cellEditors;grid;config;userConfig;#t;get defaultConfig(){return{}}constructor(t={}){this.userConfig=t}mergeConfigsFrom(t){if(0===t.length)return;const i={...this.userConfig},s={};for(const e of Object.keys(i))s[e]=this;for(const n of t){const t=n.userConfig;for(const[r,o]of Object.entries(t)){if(void 0===o)continue;if(!(r in i)){i[r]=o,s[r]=n;continue}if(i[r]===o)continue;const t=s[r]?.constructor.name??this.constructor.name,c=n.constructor.name,a=e("TBW025",`Cannot merge plugin configs for "${this.name}": conflicting value for "${r}" supplied by both ${t} and ${c}. Pass the option on a single instance, or remove the duplicate.`,void 0,this.name);throw new Error(a)}}Object.assign(this.userConfig,i)}attach(t){this.#t?.abort(),this.#t=new AbortController,this.grid=t,this.config={...this.defaultConfig,...this.userConfig}}detach(){this.#t?.abort(),this.#t=void 0}getPlugin(t){return this.grid?.getPlugin(t)}emit(t,e){this.grid?.dispatchEvent?.(new CustomEvent(t,{detail:e,bubbles:!0}))}emitCancelable(t,e){const i=new CustomEvent(t,{detail:e,bubbles:!0,cancelable:!0});return this.grid?.dispatchEvent?.(i),i.defaultPrevented}on(t,e){this.grid?._pluginManager?.subscribe(this,t,e)}off(t){this.grid?._pluginManager?.unsubscribe(this,t)}emitPluginEvent(t,e){this.grid?._pluginManager?.emitPluginEvent(t,e)}broadcast(t,e){this.emitPluginEvent(t,e),this.emit(t,e)}requestRender(){this.grid?.requestRender?.()}requestColumnsRender(){this.grid?.requestColumnsRender?.()}requestRenderWithFocus(){this.grid?.requestRenderWithFocus?.()}requestAfterRender(){this.grid?.requestAfterRender?.()}requestVirtualRefresh(){this.grid?.requestVirtualRefresh?.()}get rows(){return this.grid?.rows??[]}get sourceRows(){return this.grid?.sourceRows??[]}get columns(){return this.grid?.columns??[]}get visibleColumns(){return this.grid?._visibleColumns??[]}get gridElement(){return this.grid?._hostElement}get disconnectSignal(){return this.#t?.signal??this.grid?.disconnectSignal}get gridIcons(){const t=this.grid?.gridConfig?.icons??{};return{...a,...t}}get isAnimationEnabled(){const t=this.grid?.effectiveConfig?.animation?.mode??"reduced-motion";if(!1===t||"off"===t)return!1;if(!0===t||"on"===t)return!0;const e=this.gridElement;if(e){return"0"!==getComputedStyle(e).getPropertyValue("--tbw-animation-enabled").trim()}return!0}get animationDuration(){const t=this.gridElement;if(t){const e=getComputedStyle(t).getPropertyValue("--tbw-animation-duration").trim(),i=parseInt(e,10);if(!isNaN(i))return i}return 200}setIcon(t,e,i){t.dataset.icon=e.replace(/([A-Z])/g,"-$1").toLowerCase(),"collapse"===e?t.dataset.expanded="":"expand"===e&&delete t.dataset.expanded;const s=this.#e(e,i);void 0!==s?"string"==typeof s?t.innerHTML=c(s):s instanceof HTMLElement&&(t.innerHTML="",t.appendChild(s.cloneNode(!0))):t.innerHTML=""}#e(t,e){return void 0!==e?e:this.grid?.gridConfig?.icons?.[t]}updateSortIndicator(t,e){t.querySelector('[part~="sort-indicator"], .sort-indicator')?.remove();const i=document.createElement("span");i.setAttribute("part","sort-indicator"),i.className="sort-indicator",e?(t.setAttribute("aria-sort","asc"===e?"ascending":"descending"),t.setAttribute("data-sort",e),this.setIcon(i,"asc"===e?"sortAsc":"sortDesc")):(t.setAttribute("aria-sort","none"),t.removeAttribute("data-sort"),this.setIcon(i,"sortNone"));const s=t.querySelector(".tbw-filter-btn")??t.querySelector(".resize-handle");return s?t.insertBefore(i,s):t.appendChild(i),i}warn(i,s){void 0!==s?console.warn(e(i,s,this.gridElement.id,this.name)):console.warn(`${t(this.gridElement.id,this.name)} ${i}`)}throwDiagnostic(t,i){throw new Error(e(t,i,this.gridElement.id,this.name))}}class h extends l{name="stickyRows";styles="@layer tbw-plugins{.tbw-sticky-rows{position:absolute;top:0;left:0;display:block;z-index:var(--tbw-z-layer-sticky-rows, 22);background:var(--tbw-color-bg, var(--tbw-color-panel-bg));min-width:100%;width:fit-content;overflow:visible}.tbw-sticky-rows:empty{display:none}.tbw-sticky-rows .tbw-sticky-row{box-shadow:0 1px 0 var(--tbw-color-border);background:var(--tbw-color-bg, var(--tbw-color-panel-bg))}.tbw-sticky-rows[data-mode=push] .tbw-sticky-row{will-change:transform}}";get defaultConfig(){return{mode:"push",maxStacked:1/0}}container=null;stickyIndices=[];cloneCache=/* @__PURE__ */new Map;displayedIndices=[];lastPushOffset=0;detach(){this.container?.remove(),this.container=null,this.cloneCache.clear(),this.stickyIndices=[],this.displayedIndices=[],this.lastPushOffset=0}afterRender(){this.recomputeStickyIndices(),this.ensureContainer(),this.primeCloneCache(),this.refreshDisplay()}onScrollRender(){this.primeCloneCache(),this.refreshClonesInWindow(),this.refreshDisplay()}onScroll(t){this.refreshDisplay(t.scrollTop)}resolvePredicate(){const t=this.config.isSticky;if("function"==typeof t)return t;if("string"==typeof t){const e=t;return t=>null!=t&&"object"==typeof t&&Boolean(t[e])}return()=>!1}recomputeStickyIndices(){const t=this.rows,e=this.resolvePredicate(),i=[];for(let s=0;s<t.length;s++)e(t[s],s)&&i.push(s);(i.length!==this.stickyIndices.length||i.some((t,e)=>t!==this.stickyIndices[e]))&&this.cloneCache.clear(),this.stickyIndices=i}ensureContainer(){const t=this.gridElement;if(!t)return;const e=t.querySelector(".rows-viewport");e?this.container&&e.contains(this.container)||(this.container=document.createElement("div"),this.container.className="tbw-sticky-rows",this.container.setAttribute("role","presentation"),this.container.dataset.mode=this.config.mode??"push",this.config.className&&this.container.classList.add(this.config.className),e.insertBefore(this.container,e.firstChild)):this.container=null}offsetOf(t){const e=this.getVirtualState(),i=e?.positionCache;if(i&&i[t])return i[t].offset;return t*(e?.rowHeight??28)}heightOf(t){const e=this.getVirtualState(),i=e?.positionCache;return i&&i[t]?i[t].height:e?.rowHeight??28}getVirtualState(){return this.grid?._virtualization}getCurrentScrollTop(){const t=this.getVirtualState(),e=t?.container?.scrollTop;return"number"==typeof e?e:"number"!=typeof t?.start||"number"!=typeof t?.rowHeight||t.positionCache&&t.positionCache.length?t?.positionCache&&"number"==typeof t.start&&t.positionCache[t.start]?t.positionCache[t.start].offset:0:t.start*t.rowHeight}computeDisplay(t){if("stack"===this.config.mode){const e=this.config.maxStacked??1/0,i=[];let s=0;for(const n of this.stickyIndices)if(i.length<e){if(!(this.offsetOf(n)<t+s))break;i.push(n),s+=this.heightOf(n)}else{const e=this.heightOf(i[0]);if(!(this.offsetOf(n)<t+s-e))break;i.shift(),s-=e,i.push(n),s+=this.heightOf(n)}if(i.length===e&&i.length>0){const e=this.findNextSticky(i[i.length-1]);if(null!=e){const n=this.offsetOf(e)-(t+s);if(n<0){const t=this.heightOf(i[0]);return{indices:i,pushOffset:Math.min(-n,t)}}}}return{indices:i,pushOffset:0}}const e=[];for(const r of this.stickyIndices){if(!(this.offsetOf(r)<t))break;e.push(r)}if(0===e.length)return{indices:e,pushOffset:0};const i=e[e.length-1],s=this.findNextSticky(i);let n=0;if(null!=s){const e=this.heightOf(i),r=this.offsetOf(s)-t;r<e&&(n=e-Math.max(0,r))}return{indices:[i],pushOffset:n}}findRenderedRow(t){const e=this.gridElement;if(!e)return null;const i=e.querySelector(`.rows .data-grid-row .cell[data-row="${t}"]`);return i?.parentElement??null}buildClone(t){const e=this.findRenderedRow(t);if(!e)return this.cloneCache.get(t)??null;const i=e.cloneNode(!0);return i.classList.add("tbw-sticky-row"),i.removeAttribute("aria-rowindex"),i.setAttribute("aria-hidden","true"),i.dataset.stickyRow=String(t),i.classList.remove("row-focus","cell-focus"),i.querySelectorAll(".cell-focus, .row-focus").forEach(t=>t.classList.remove("cell-focus","row-focus")),i.removeAttribute("tabindex"),i.querySelectorAll("[tabindex]").forEach(t=>t.removeAttribute("tabindex")),this.cloneCache.set(t,i),i}refreshClonesInWindow(){if(this.displayedIndices.length)for(const t of this.displayedIndices){if(this.findRenderedRow(t)){const e=this.buildClone(t),i=this.container?.querySelector(`[data-sticky-row="${t}"]`);e&&i&&i!==e&&i.replaceWith(e)}}}primeCloneCache(){if(this.stickyIndices.length)for(const t of this.stickyIndices)this.findRenderedRow(t)&&this.buildClone(t)}refreshDisplay(t){if(!this.container)return;const e=t??this.getCurrentScrollTop(),{indices:i,pushOffset:s}=this.computeDisplay(e);this.container.dataset.mode!==this.config.mode&&(this.container.dataset.mode=this.config.mode??"push");if(!(i.length===this.displayedIndices.length&&i.every((t,e)=>t===this.displayedIndices[e]))){const t=document.createDocumentFragment(),e=[];for(const s of i){let i=this.cloneCache.get(s)??null;i=this.buildClone(s)??i,i&&(t.appendChild(i),e.push(s))}this.container.replaceChildren(t),this.displayedIndices=e}s!==this.lastPushOffset&&(this.container.style.transform=s>0?`translateY(${-s}px)`:"",this.lastPushOffset=s)}findNextSticky(t){for(const e of this.stickyIndices)if(e>t)return e;return null}}export{h as StickyRowsPlugin};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|