@toolbox-web/grid 1.0.0 → 1.1.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.d.ts.map +1 -1
- package/all.js +1655 -1444
- package/all.js.map +1 -1
- package/index.js +438 -401
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/internal/validate-config.d.ts +10 -0
- package/lib/core/internal/validate-config.d.ts.map +1 -1
- package/lib/core/plugin/base-plugin.d.ts +101 -0
- package/lib/core/plugin/base-plugin.d.ts.map +1 -1
- package/lib/core/plugin/index.d.ts +1 -1
- package/lib/core/plugin/index.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +22 -0
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +48 -26
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +64 -42
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts +6 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +50 -4
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js +36 -14
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js +63 -41
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +6 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-columns/index.js +47 -6
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +22 -0
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +22 -0
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +22 -0
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +6 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/index.js +36 -0
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +22 -0
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +22 -0
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +22 -0
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/ResponsivePlugin.d.ts +123 -0
- package/lib/plugins/responsive/ResponsivePlugin.d.ts.map +1 -0
- package/lib/plugins/responsive/index.d.ts +11 -0
- package/lib/plugins/responsive/index.d.ts.map +1 -0
- package/lib/plugins/responsive/index.js +589 -0
- package/lib/plugins/responsive/index.js.map +1 -0
- package/lib/plugins/responsive/types.d.ts +133 -0
- package/lib/plugins/responsive/types.d.ts.map +1 -0
- package/lib/plugins/selection/SelectionPlugin.d.ts +6 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.d.ts +1 -1
- package/lib/plugins/selection/index.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +105 -63
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/selection/types.d.ts +26 -0
- package/lib/plugins/selection/types.d.ts.map +1 -1
- package/lib/plugins/server-side/index.js +22 -0
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +22 -0
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +27 -5
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +22 -0
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +28 -22
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +18 -14
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/grouping-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/pinned-columns.umd.js +1 -1
- package/umd/plugins/pinned-columns.umd.js.map +1 -1
- package/umd/plugins/responsive.umd.js +2 -0
- package/umd/plugins/responsive.umd.js.map +1 -0
- package/umd/plugins/selection.umd.js +3 -1
- package/umd/plugins/selection.umd.js.map +1 -1
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
function b(r) {
|
|
2
|
+
r && r.querySelectorAll(".cell-focus").forEach((t) => t.classList.remove("cell-focus"));
|
|
3
|
+
}
|
|
4
|
+
const w = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])', m = document.createElement("template");
|
|
5
|
+
m.innerHTML = '<div class="cell" role="gridcell" part="cell"></div>';
|
|
6
|
+
const y = document.createElement("template");
|
|
7
|
+
y.innerHTML = '<div class="data-grid-row" role="row" part="row"></div>';
|
|
8
|
+
function u(r, t) {
|
|
9
|
+
if (r._virtualization?.enabled) {
|
|
10
|
+
const { rowHeight: a, container: s, viewportEl: d } = r._virtualization, n = s, c = d?.clientHeight ?? n?.clientHeight ?? 0;
|
|
11
|
+
if (n && c > 0) {
|
|
12
|
+
const h = r._focusRow * a;
|
|
13
|
+
h < n.scrollTop ? n.scrollTop = h : h + a > n.scrollTop + c && (n.scrollTop = h - c + a);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const e = r._activeEditRows !== void 0 && r._activeEditRows !== -1;
|
|
17
|
+
e || r.refreshVirtualWindow(!1), b(r._bodyEl), Array.from(r._bodyEl.querySelectorAll('[aria-selected="true"]')).forEach((a) => {
|
|
18
|
+
a.setAttribute("aria-selected", "false");
|
|
19
|
+
});
|
|
20
|
+
const i = r._focusRow, o = r._virtualization.start ?? 0, l = r._virtualization.end ?? r._rows.length;
|
|
21
|
+
if (i >= o && i < l) {
|
|
22
|
+
const a = r._bodyEl.querySelectorAll(".data-grid-row")[i - o];
|
|
23
|
+
let s = a?.children[r._focusCol];
|
|
24
|
+
if ((!s || !s.classList?.contains("cell")) && (s = a?.querySelector(`.cell[data-col="${r._focusCol}"]`) ?? a?.querySelector(".cell[data-col]")), s) {
|
|
25
|
+
s.classList.add("cell-focus"), s.setAttribute("aria-selected", "true");
|
|
26
|
+
const d = r.querySelector(".tbw-scroll-area");
|
|
27
|
+
if (d && s && !e) {
|
|
28
|
+
const n = r._getHorizontalScrollOffsets?.(a ?? void 0, s) ?? { left: 0, right: 0 };
|
|
29
|
+
if (!n.skipScroll) {
|
|
30
|
+
const c = s.getBoundingClientRect(), h = d.getBoundingClientRect(), g = c.left - h.left + d.scrollLeft, p = g + c.width, f = d.scrollLeft + n.left, v = d.scrollLeft + d.clientWidth - n.right;
|
|
31
|
+
g < f ? d.scrollLeft = g - n.left : p > v && (d.scrollLeft = p - d.clientWidth + n.right);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (r._activeEditRows !== void 0 && r._activeEditRows !== -1 && s.classList.contains("editing")) {
|
|
35
|
+
const n = s.querySelector(w);
|
|
36
|
+
if (n && document.activeElement !== n)
|
|
37
|
+
try {
|
|
38
|
+
n.focus({ preventScroll: !0 });
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
} else if (!s.contains(document.activeElement)) {
|
|
42
|
+
s.hasAttribute("tabindex") || s.setAttribute("tabindex", "-1");
|
|
43
|
+
try {
|
|
44
|
+
s.focus({ preventScroll: !0 });
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const R = {
|
|
52
|
+
expand: "▶",
|
|
53
|
+
collapse: "▼",
|
|
54
|
+
sortAsc: "▲",
|
|
55
|
+
sortDesc: "▼",
|
|
56
|
+
sortNone: "⇅",
|
|
57
|
+
submenuArrow: "▶",
|
|
58
|
+
dragHandle: "⋮⋮",
|
|
59
|
+
toolPanel: "☰"
|
|
60
|
+
};
|
|
61
|
+
class _ {
|
|
62
|
+
/**
|
|
63
|
+
* Plugin dependencies - declare other plugins this one requires.
|
|
64
|
+
*
|
|
65
|
+
* Dependencies are validated when the plugin is attached.
|
|
66
|
+
* Required dependencies throw an error if missing.
|
|
67
|
+
* Optional dependencies log an info message if missing.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* static readonly dependencies: PluginDependency[] = [
|
|
72
|
+
* { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
|
|
73
|
+
* { name: 'selection', required: false, reason: 'Enables selection-based undo' },
|
|
74
|
+
* ];
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
static dependencies;
|
|
78
|
+
/**
|
|
79
|
+
* Plugin manifest - declares owned properties, config rules, and hook priorities.
|
|
80
|
+
*
|
|
81
|
+
* This is read by the configuration validator to:
|
|
82
|
+
* - Validate that required plugins are loaded when their properties are used
|
|
83
|
+
* - Execute configRules to detect invalid/conflicting settings
|
|
84
|
+
* - Order hook execution based on priority
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* static override readonly manifest: PluginManifest<MyConfig> = {
|
|
89
|
+
* ownedProperties: [
|
|
90
|
+
* { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
|
|
91
|
+
* ],
|
|
92
|
+
* configRules: [
|
|
93
|
+
* { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
|
|
94
|
+
* ],
|
|
95
|
+
* };
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
static manifest;
|
|
100
|
+
/**
|
|
101
|
+
* Plugin version - defaults to grid version for built-in plugins.
|
|
102
|
+
* Third-party plugins can override with their own semver.
|
|
103
|
+
*/
|
|
104
|
+
version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
|
|
105
|
+
/** CSS styles to inject into the grid's shadow DOM */
|
|
106
|
+
styles;
|
|
107
|
+
/** Custom cell renderers keyed by type name */
|
|
108
|
+
cellRenderers;
|
|
109
|
+
/** Custom header renderers keyed by type name */
|
|
110
|
+
headerRenderers;
|
|
111
|
+
/** Custom cell editors keyed by type name */
|
|
112
|
+
cellEditors;
|
|
113
|
+
/** The grid instance this plugin is attached to */
|
|
114
|
+
grid;
|
|
115
|
+
/** Plugin configuration - merged with defaults in attach() */
|
|
116
|
+
config;
|
|
117
|
+
/** User-provided configuration from constructor */
|
|
118
|
+
userConfig;
|
|
119
|
+
/**
|
|
120
|
+
* Plugin-level AbortController for event listener cleanup.
|
|
121
|
+
* Created fresh in attach(), aborted in detach().
|
|
122
|
+
* This ensures event listeners are properly cleaned up when plugins are re-attached.
|
|
123
|
+
*/
|
|
124
|
+
#e;
|
|
125
|
+
/**
|
|
126
|
+
* Default configuration - subclasses should override this getter.
|
|
127
|
+
* Note: This must be a getter (not property initializer) for proper inheritance
|
|
128
|
+
* since property initializers run after parent constructor.
|
|
129
|
+
*/
|
|
130
|
+
get defaultConfig() {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
constructor(t = {}) {
|
|
134
|
+
this.userConfig = t;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Called when the plugin is attached to a grid.
|
|
138
|
+
* Override to set up event listeners, initialize state, etc.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* attach(grid: GridElement): void {
|
|
143
|
+
* super.attach(grid);
|
|
144
|
+
* // Set up document-level listeners with auto-cleanup
|
|
145
|
+
* document.addEventListener('keydown', this.handleEscape, {
|
|
146
|
+
* signal: this.disconnectSignal
|
|
147
|
+
* });
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
attach(t) {
|
|
152
|
+
this.#e?.abort(), this.#e = new AbortController(), this.grid = t, this.config = { ...this.defaultConfig, ...this.userConfig };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Called when the plugin is detached from a grid.
|
|
156
|
+
* Override to clean up event listeners, timers, etc.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* detach(): void {
|
|
161
|
+
* // Clean up any state not handled by disconnectSignal
|
|
162
|
+
* this.selectedRows.clear();
|
|
163
|
+
* this.cache = null;
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
detach() {
|
|
168
|
+
this.#e?.abort(), this.#e = void 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get another plugin instance from the same grid.
|
|
172
|
+
* Use for inter-plugin communication.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* const selection = this.getPlugin(SelectionPlugin);
|
|
177
|
+
* if (selection) {
|
|
178
|
+
* const selectedRows = selection.getSelectedRows();
|
|
179
|
+
* }
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
getPlugin(t) {
|
|
183
|
+
return this.grid?.getPlugin(t);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Emit a custom event from the grid.
|
|
187
|
+
*/
|
|
188
|
+
emit(t, e) {
|
|
189
|
+
this.grid?.dispatchEvent?.(new CustomEvent(t, { detail: e, bubbles: !0 }));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Emit a cancelable custom event from the grid.
|
|
193
|
+
* @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
|
|
194
|
+
*/
|
|
195
|
+
emitCancelable(t, e) {
|
|
196
|
+
const i = new CustomEvent(t, { detail: e, bubbles: !0, cancelable: !0 });
|
|
197
|
+
return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Request a re-render of the grid.
|
|
201
|
+
*/
|
|
202
|
+
requestRender() {
|
|
203
|
+
this.grid?.requestRender?.();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Request a re-render and restore focus styling afterward.
|
|
207
|
+
* Use this when a plugin action (like expand/collapse) triggers a render
|
|
208
|
+
* but needs to maintain keyboard navigation focus.
|
|
209
|
+
*/
|
|
210
|
+
requestRenderWithFocus() {
|
|
211
|
+
this.grid?.requestRenderWithFocus?.();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Request a lightweight style update without rebuilding DOM.
|
|
215
|
+
* Use this instead of requestRender() when only CSS classes need updating.
|
|
216
|
+
*/
|
|
217
|
+
requestAfterRender() {
|
|
218
|
+
this.grid?.requestAfterRender?.();
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get the current rows from the grid.
|
|
222
|
+
*/
|
|
223
|
+
get rows() {
|
|
224
|
+
return this.grid?.rows ?? [];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get the original unfiltered/unprocessed rows from the grid.
|
|
228
|
+
* Use this when you need all source data regardless of active filters.
|
|
229
|
+
*/
|
|
230
|
+
get sourceRows() {
|
|
231
|
+
return this.grid?.sourceRows ?? [];
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get the current columns from the grid.
|
|
235
|
+
*/
|
|
236
|
+
get columns() {
|
|
237
|
+
return this.grid?.columns ?? [];
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get only visible columns from the grid (excludes hidden).
|
|
241
|
+
* Use this for rendering that needs to match the grid template.
|
|
242
|
+
*/
|
|
243
|
+
get visibleColumns() {
|
|
244
|
+
return this.grid?._visibleColumns ?? [];
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get the grid as an HTMLElement for direct DOM operations.
|
|
248
|
+
* Use sparingly - prefer the typed GridElementRef API when possible.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* const width = this.gridElement.clientWidth;
|
|
253
|
+
* this.gridElement.classList.add('my-plugin-active');
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
get gridElement() {
|
|
257
|
+
return this.grid;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get the disconnect signal for event listener cleanup.
|
|
261
|
+
* This signal is aborted when the grid disconnects from the DOM.
|
|
262
|
+
* Use this when adding event listeners that should be cleaned up automatically.
|
|
263
|
+
*
|
|
264
|
+
* Best for:
|
|
265
|
+
* - Document/window-level listeners added in attach()
|
|
266
|
+
* - Listeners on the grid element itself
|
|
267
|
+
* - Any listener that should persist across renders
|
|
268
|
+
*
|
|
269
|
+
* Not needed for:
|
|
270
|
+
* - Listeners on elements created in afterRender() (removed with element)
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* element.addEventListener('click', handler, { signal: this.disconnectSignal });
|
|
274
|
+
* document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
|
|
275
|
+
*/
|
|
276
|
+
get disconnectSignal() {
|
|
277
|
+
return this.#e?.signal ?? this.grid?.disconnectSignal;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the grid-level icons configuration.
|
|
281
|
+
* Returns merged icons (user config + defaults).
|
|
282
|
+
*/
|
|
283
|
+
get gridIcons() {
|
|
284
|
+
const t = this.grid?.gridConfig?.icons ?? {};
|
|
285
|
+
return { ...R, ...t };
|
|
286
|
+
}
|
|
287
|
+
// #region Animation Helpers
|
|
288
|
+
/**
|
|
289
|
+
* Check if animations are enabled at the grid level.
|
|
290
|
+
* Respects gridConfig.animation.mode and the CSS variable set by the grid.
|
|
291
|
+
*
|
|
292
|
+
* Plugins should use this to skip animations when:
|
|
293
|
+
* - Animation mode is 'off' or `false`
|
|
294
|
+
* - User prefers reduced motion and mode is 'reduced-motion' (default)
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```ts
|
|
298
|
+
* private get animationStyle(): 'slide' | 'fade' | false {
|
|
299
|
+
* if (!this.isAnimationEnabled) return false;
|
|
300
|
+
* return this.config.animation ?? 'slide';
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
get isAnimationEnabled() {
|
|
305
|
+
const t = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
|
|
306
|
+
if (t === !1 || t === "off") return !1;
|
|
307
|
+
if (t === !0 || t === "on") return !0;
|
|
308
|
+
const e = this.gridElement;
|
|
309
|
+
return e ? getComputedStyle(e).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get the animation duration in milliseconds from CSS variable.
|
|
313
|
+
* Falls back to 200ms if not set.
|
|
314
|
+
*
|
|
315
|
+
* Plugins can use this for their animation timing to stay consistent
|
|
316
|
+
* with the grid-level animation.duration setting.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```ts
|
|
320
|
+
* element.animate(keyframes, { duration: this.animationDuration });
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
get animationDuration() {
|
|
324
|
+
const t = this.gridElement;
|
|
325
|
+
if (t) {
|
|
326
|
+
const e = getComputedStyle(t).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(e, 10);
|
|
327
|
+
if (!isNaN(i)) return i;
|
|
328
|
+
}
|
|
329
|
+
return 200;
|
|
330
|
+
}
|
|
331
|
+
// #endregion
|
|
332
|
+
/**
|
|
333
|
+
* Resolve an icon value to string or HTMLElement.
|
|
334
|
+
* Checks plugin config first, then grid-level icons, then defaults.
|
|
335
|
+
*
|
|
336
|
+
* @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
|
|
337
|
+
* @param pluginOverride - Optional plugin-level override
|
|
338
|
+
* @returns The resolved icon value
|
|
339
|
+
*/
|
|
340
|
+
resolveIcon(t, e) {
|
|
341
|
+
return e !== void 0 ? e : this.gridIcons[t];
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Set an icon value on an element.
|
|
345
|
+
* Handles both string (text/HTML) and HTMLElement values.
|
|
346
|
+
*
|
|
347
|
+
* @param element - The element to set the icon on
|
|
348
|
+
* @param icon - The icon value (string or HTMLElement)
|
|
349
|
+
*/
|
|
350
|
+
setIcon(t, e) {
|
|
351
|
+
typeof e == "string" ? t.innerHTML = e : e instanceof HTMLElement && (t.innerHTML = "", t.appendChild(e.cloneNode(!0)));
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Log a warning message.
|
|
355
|
+
*/
|
|
356
|
+
warn(t) {
|
|
357
|
+
console.warn(`[tbw-grid:${this.name}] ${t}`);
|
|
358
|
+
}
|
|
359
|
+
// #endregion
|
|
360
|
+
}
|
|
361
|
+
const C = 'tbw-grid[data-responsive-animate] .data-grid-row,tbw-grid[data-responsive-animate] .data-grid-row>.cell{transition:opacity var(--tbw-responsive-duration, .2s) ease-out,transform var(--tbw-responsive-duration, .2s) ease-out}tbw-grid[data-responsive][data-responsive-animate] .data-grid-row{animation:responsive-card-enter var(--tbw-responsive-duration, .2s) ease-out}@keyframes responsive-card-enter{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}tbw-grid[data-responsive] .header{display:none!important}tbw-grid[data-responsive] .footer-row{display:none}tbw-grid[data-responsive] .tbw-scroll-area{overflow-x:hidden;min-width:0!important}tbw-grid[data-responsive] .rows-body-wrapper{min-width:0!important}tbw-grid[data-responsive] .data-grid-row{display:block!important;grid-template-columns:none!important;padding:var(--tbw-cell-padding);padding-left:var(--tbw-spacing-xl);border-bottom:1px solid var(--tbw-color-border);min-height:auto!important;height:auto!important;contain:none!important;content-visibility:visible!important;background:var(--tbw-color-bg);position:relative}tbw-grid[data-responsive] .data-grid-row:nth-child(2n){background:var(--tbw-color-row-alt)}tbw-grid[data-responsive] .data-grid-row:hover{background:var(--tbw-color-row-hover)}tbw-grid[data-responsive] .data-grid-row[aria-selected=true]{background:var(--tbw-color-selection)}tbw-grid[data-responsive] .data-grid-row[aria-selected=true]:before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--tbw-color-accent)}tbw-grid[data-responsive] .data-grid-row>.cell{display:flex!important;justify-content:space-between;align-items:center;padding:var(--tbw-spacing-xs) var(--tbw-spacing-md);width:100%!important;min-width:0!important;min-height:auto!important;height:auto!important;line-height:1.5!important;position:static!important;left:auto!important;right:auto!important;border:none!important;border-bottom:none!important;border-right:none!important;background:transparent!important;white-space:normal!important;overflow:visible!important}tbw-grid[data-responsive] .data-grid-row>.cell:before{content:attr(data-header) ": ";font-weight:600;color:var(--tbw-color-header-fg);flex-shrink:0;margin-right:var(--tbw-spacing-md);min-width:100px}tbw-grid[data-responsive] .data-grid-row>.cell:after{content:none}tbw-grid[data-responsive] .cell[data-utility]{display:none!important}tbw-grid[data-responsive] .data-grid-row:not(:last-child){margin-bottom:var(--tbw-spacing-xs)}tbw-grid[data-responsive] .cell[data-responsive-hidden]{display:none!important}tbw-grid[data-responsive] .cell[data-responsive-value-only]{justify-content:flex-start!important;font-weight:500}tbw-grid[data-responsive] .cell[data-responsive-value-only]:before{display:none!important}tbw-grid:not([data-responsive]) .cell[data-responsive-hidden]{display:none!important}tbw-grid[data-responsive] .tbw-footer,tbw-grid[data-responsive] .tbw-pinned-rows,tbw-grid[data-responsive] .tbw-aggregation-rows{display:none!important}tbw-grid[data-responsive] .tbw-pinned-rows,tbw-grid[data-responsive] .tbw-aggregation-rows,tbw-grid[data-responsive] .tbw-aggregation-row{min-width:0!important}tbw-grid[data-responsive] .data-grid-row.responsive-card{display:block!important;padding:var(--tbw-cell-padding);border-bottom:1px solid var(--tbw-color-border)}tbw-grid[data-responsive] .data-grid-row.responsive-card>*{width:100%}tbw-grid[data-responsive] .data-grid-row.responsive-card .cell:before{display:none}';
|
|
362
|
+
class A extends _ {
|
|
363
|
+
name = "responsive";
|
|
364
|
+
version = "1.0.0";
|
|
365
|
+
styles = C;
|
|
366
|
+
#e;
|
|
367
|
+
#t = !1;
|
|
368
|
+
#s;
|
|
369
|
+
#h = !1;
|
|
370
|
+
#o = 0;
|
|
371
|
+
/** Set of column fields to completely hide */
|
|
372
|
+
#i = /* @__PURE__ */ new Set();
|
|
373
|
+
/** Set of column fields to show value only (no header label) */
|
|
374
|
+
#n = /* @__PURE__ */ new Set();
|
|
375
|
+
/** Currently active breakpoint, or null if none */
|
|
376
|
+
#a = null;
|
|
377
|
+
/** Sorted breakpoints from largest to smallest */
|
|
378
|
+
#l = [];
|
|
379
|
+
/**
|
|
380
|
+
* Check if currently in responsive mode.
|
|
381
|
+
* @returns `true` if the grid is in card layout mode
|
|
382
|
+
*/
|
|
383
|
+
isResponsive() {
|
|
384
|
+
return this.#t;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Force responsive mode regardless of width.
|
|
388
|
+
* Useful for testing or manual control.
|
|
389
|
+
* @param enabled - Whether to enable responsive mode
|
|
390
|
+
*/
|
|
391
|
+
setResponsive(t) {
|
|
392
|
+
t !== this.#t && (this.#t = t, this.#c(), this.emit("responsive-change", {
|
|
393
|
+
isResponsive: t,
|
|
394
|
+
width: this.#o,
|
|
395
|
+
breakpoint: this.config.breakpoint ?? 0
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Update breakpoint dynamically.
|
|
400
|
+
* @param width - New breakpoint width in pixels
|
|
401
|
+
*/
|
|
402
|
+
setBreakpoint(t) {
|
|
403
|
+
this.config.breakpoint = t, this.#u(this.#o);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Set a custom card renderer.
|
|
407
|
+
* This allows framework adapters to provide template-based renderers at runtime.
|
|
408
|
+
* @param renderer - The card renderer function, or undefined to use default
|
|
409
|
+
*/
|
|
410
|
+
setCardRenderer(t) {
|
|
411
|
+
this.config.cardRenderer = t, this.#t && this.requestRender();
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get current grid width.
|
|
415
|
+
* @returns Width of the grid element in pixels
|
|
416
|
+
*/
|
|
417
|
+
getWidth() {
|
|
418
|
+
return this.#o;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get the currently active breakpoint config (multi-breakpoint mode only).
|
|
422
|
+
* @returns The active BreakpointConfig, or null if no breakpoint is active
|
|
423
|
+
*/
|
|
424
|
+
getActiveBreakpoint() {
|
|
425
|
+
return this.#a;
|
|
426
|
+
}
|
|
427
|
+
attach(t) {
|
|
428
|
+
super.attach(t), this.#d(this.config.hiddenColumns), this.config.breakpoints?.length && (this.#l = [...this.config.breakpoints].sort((e, i) => i.maxWidth - e.maxWidth)), this.#e = new ResizeObserver((e) => {
|
|
429
|
+
const i = e[0]?.contentRect.width ?? 0;
|
|
430
|
+
this.#o = i, clearTimeout(this.#s), this.#s = setTimeout(() => {
|
|
431
|
+
this.#u(i);
|
|
432
|
+
}, this.config.debounceMs ?? 100);
|
|
433
|
+
}), this.#e.observe(this.gridElement);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Build the hidden and value-only column sets from config.
|
|
437
|
+
*/
|
|
438
|
+
#d(t) {
|
|
439
|
+
if (this.#i.clear(), this.#n.clear(), !!t)
|
|
440
|
+
for (const e of t)
|
|
441
|
+
typeof e == "string" ? this.#i.add(e) : e.showValue ? this.#n.add(e.field) : this.#i.add(e.field);
|
|
442
|
+
}
|
|
443
|
+
detach() {
|
|
444
|
+
this.#e?.disconnect(), this.#e = void 0, clearTimeout(this.#s), this.#s = void 0, this.gridElement && this.gridElement.removeAttribute("data-responsive"), super.detach();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Apply hidden and value-only columns.
|
|
448
|
+
* In legacy mode (single breakpoint), only applies when in responsive mode.
|
|
449
|
+
* In multi-breakpoint mode, applies whenever there's an active breakpoint.
|
|
450
|
+
*/
|
|
451
|
+
afterRender() {
|
|
452
|
+
if (!(this.#l.length > 0 ? this.#a !== null : this.#t))
|
|
453
|
+
return;
|
|
454
|
+
const e = this.#i.size > 0, i = this.#n.size > 0;
|
|
455
|
+
if (!e && !i)
|
|
456
|
+
return;
|
|
457
|
+
const o = this.gridElement.querySelectorAll(".cell[data-field]");
|
|
458
|
+
for (const l of o) {
|
|
459
|
+
const a = l.getAttribute("data-field");
|
|
460
|
+
a && (this.#i.has(a) ? (l.setAttribute("data-responsive-hidden", ""), l.removeAttribute("data-responsive-value-only")) : this.#n.has(a) ? (l.setAttribute("data-responsive-value-only", ""), l.removeAttribute("data-responsive-hidden")) : (l.removeAttribute("data-responsive-hidden"), l.removeAttribute("data-responsive-value-only")));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Check if width has crossed any breakpoint threshold.
|
|
465
|
+
* Handles both single breakpoint (legacy) and multi-breakpoint modes.
|
|
466
|
+
*/
|
|
467
|
+
#u(t) {
|
|
468
|
+
if (this.#l.length > 0) {
|
|
469
|
+
this.#g(t);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const e = this.config.breakpoint ?? 0;
|
|
473
|
+
e === 0 && !this.#h && (this.#h = !0, console.warn(
|
|
474
|
+
"[tbw-grid:ResponsivePlugin] No breakpoint configured. Responsive mode is disabled. Set a breakpoint based on your grid's column count."
|
|
475
|
+
));
|
|
476
|
+
const i = e > 0 && t < e;
|
|
477
|
+
i !== this.#t && (this.#t = i, this.#c(), this.emit("responsive-change", {
|
|
478
|
+
isResponsive: i,
|
|
479
|
+
width: t,
|
|
480
|
+
breakpoint: e
|
|
481
|
+
}), this.requestRender());
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Check breakpoints in multi-breakpoint mode.
|
|
485
|
+
* Evaluates breakpoints from largest to smallest, applying the first match.
|
|
486
|
+
*/
|
|
487
|
+
#g(t) {
|
|
488
|
+
let e = null;
|
|
489
|
+
for (const o of this.#l)
|
|
490
|
+
t <= o.maxWidth && (e = o);
|
|
491
|
+
if (e !== this.#a) {
|
|
492
|
+
this.#a = e, e?.hiddenColumns ? this.#d(e.hiddenColumns) : this.#d(this.config.hiddenColumns);
|
|
493
|
+
const o = e?.cardLayout === !0;
|
|
494
|
+
o !== this.#t && (this.#t = o, this.#c()), this.emit("responsive-change", {
|
|
495
|
+
isResponsive: this.#t,
|
|
496
|
+
width: t,
|
|
497
|
+
breakpoint: e?.maxWidth ?? 0
|
|
498
|
+
}), this.requestRender();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/** Original row height before entering responsive mode, for restoration on exit */
|
|
502
|
+
#r;
|
|
503
|
+
/**
|
|
504
|
+
* Apply the responsive state to the grid element.
|
|
505
|
+
* Handles scroll reset when entering responsive mode and row height restoration on exit.
|
|
506
|
+
*/
|
|
507
|
+
#c() {
|
|
508
|
+
this.gridElement.toggleAttribute("data-responsive", this.#t);
|
|
509
|
+
const t = this.config.animate !== !1;
|
|
510
|
+
this.gridElement.toggleAttribute("data-responsive-animate", t), this.config.animationDuration && this.gridElement.style.setProperty("--tbw-responsive-duration", `${this.config.animationDuration}ms`);
|
|
511
|
+
const e = this.grid;
|
|
512
|
+
if (this.#t) {
|
|
513
|
+
e._virtualization && (this.#r = e._virtualization.rowHeight);
|
|
514
|
+
const i = this.gridElement.querySelector(".tbw-scroll-area");
|
|
515
|
+
i && (i.scrollLeft = 0);
|
|
516
|
+
} else {
|
|
517
|
+
const i = this.gridElement.querySelectorAll(".data-grid-row");
|
|
518
|
+
for (const o of i)
|
|
519
|
+
o.style.height = "", o.classList.remove("responsive-card");
|
|
520
|
+
this.#r && this.#r > 0 && e._virtualization && (e._virtualization.rowHeight = this.#r, this.#r = void 0);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Custom row rendering when cardRenderer is provided and in responsive mode.
|
|
525
|
+
*
|
|
526
|
+
* When a cardRenderer is configured, this hook takes over row rendering to display
|
|
527
|
+
* the custom card layout instead of the default cell structure.
|
|
528
|
+
*
|
|
529
|
+
* @param row - The row data object
|
|
530
|
+
* @param rowEl - The row DOM element to render into
|
|
531
|
+
* @param rowIndex - The index of the row in the data array
|
|
532
|
+
* @returns `true` if rendered (prevents default), `void` for default rendering
|
|
533
|
+
*/
|
|
534
|
+
renderRow(t, e, i) {
|
|
535
|
+
if (!this.#t || !this.config.cardRenderer)
|
|
536
|
+
return;
|
|
537
|
+
e.replaceChildren();
|
|
538
|
+
const o = this.config.cardRenderer(t, i);
|
|
539
|
+
e.classList.add("responsive-card");
|
|
540
|
+
const l = this.config.cardRowHeight ?? "auto";
|
|
541
|
+
return l !== "auto" ? e.style.height = `${l}px` : e.style.height = "auto", e.appendChild(o), !0;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Handle keyboard navigation in responsive mode.
|
|
545
|
+
*
|
|
546
|
+
* In responsive mode, the visual layout is inverted:
|
|
547
|
+
* - Cells are stacked vertically within each "card" (row)
|
|
548
|
+
* - DOWN/UP visually moves within the card (between fields)
|
|
549
|
+
* - Page Down/Page Up or Ctrl+Down/Up moves between cards
|
|
550
|
+
*
|
|
551
|
+
* For custom cardRenderers, keyboard navigation is disabled entirely
|
|
552
|
+
* since the implementor controls the card content and should handle
|
|
553
|
+
* navigation via their own event handlers.
|
|
554
|
+
*
|
|
555
|
+
* @returns `true` if the event was handled and default behavior should be prevented
|
|
556
|
+
*/
|
|
557
|
+
onKeyDown(t) {
|
|
558
|
+
if (!this.#t || this.config.cardRenderer && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(t.key))
|
|
559
|
+
return !1;
|
|
560
|
+
const e = this.rows.length - 1, i = this.visibleColumns.length - 1;
|
|
561
|
+
switch (t.key) {
|
|
562
|
+
case "ArrowDown":
|
|
563
|
+
if (this.grid._focusCol < i)
|
|
564
|
+
return this.grid._focusCol += 1, t.preventDefault(), u(this.grid), !0;
|
|
565
|
+
if (this.grid._focusRow < e)
|
|
566
|
+
return this.grid._focusRow += 1, this.grid._focusCol = 0, t.preventDefault(), u(this.grid), !0;
|
|
567
|
+
break;
|
|
568
|
+
case "ArrowUp":
|
|
569
|
+
if (this.grid._focusCol > 0)
|
|
570
|
+
return this.grid._focusCol -= 1, t.preventDefault(), u(this.grid), !0;
|
|
571
|
+
if (this.grid._focusRow > 0)
|
|
572
|
+
return this.grid._focusRow -= 1, this.grid._focusCol = i, t.preventDefault(), u(this.grid), !0;
|
|
573
|
+
break;
|
|
574
|
+
case "ArrowRight":
|
|
575
|
+
if (this.grid._focusRow < e)
|
|
576
|
+
return this.grid._focusRow += 1, t.preventDefault(), u(this.grid), !0;
|
|
577
|
+
break;
|
|
578
|
+
case "ArrowLeft":
|
|
579
|
+
if (this.grid._focusRow > 0)
|
|
580
|
+
return this.grid._focusRow -= 1, t.preventDefault(), u(this.grid), !0;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
return !1;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
export {
|
|
587
|
+
A as ResponsivePlugin
|
|
588
|
+
};
|
|
589
|
+
//# sourceMappingURL=index.js.map
|