vueless 1.3.9-beta.8 → 1.4.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.
@@ -1,20 +1,32 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, useTemplateRef } from "vue";
2
+ import {
3
+ Comment,
4
+ Fragment,
5
+ Text,
6
+ computed,
7
+ h,
8
+ onMounted,
9
+ useAttrs,
10
+ useSlots,
11
+ useTemplateRef,
12
+ } from "vue";
13
+
14
+ import { useUI } from "../composables/useUI";
15
+ import { useMutationObserver } from "../composables/useMutationObserver";
16
+
17
+ import { isEmptyValue } from "../utils/helper";
3
18
  import { cx } from "../utils/ui";
4
- import { hasSlotContent, isEmptyValue } from "../utils/helper";
5
19
 
6
20
  import { PX_IN_REM } from "../constants";
7
- import { mapRowColumns } from "./utilTable";
8
21
 
9
- import { useMutationObserver } from "../composables/useMutationObserver";
10
- import { useUI } from "../composables/useUI";
22
+ import defaultConfig from "./config";
23
+ import { mapRowColumns } from "./utilTable";
24
+ import { StickySide } from "./types";
11
25
 
12
- import UIcon from "../ui.image-icon/UIcon.vue";
13
26
  import UCheckbox from "../ui.form-checkbox/UCheckbox.vue";
27
+ import UIcon from "../ui.image-icon/UIcon.vue";
14
28
 
15
- import defaultConfig from "./config";
16
-
17
- import { StickySide } from "./types";
29
+ import type { Slot, VNode } from "vue";
18
30
  import type { Cell, CellObject, Row, UTableRowProps, Config, ColumnObject } from "./types";
19
31
 
20
32
  const NESTED_ROW_SHIFT_REM = 1.5;
@@ -24,17 +36,19 @@ defineOptions({ internal: true });
24
36
 
25
37
  const props = defineProps<UTableRowProps>();
26
38
 
27
- const emit = defineEmits(["click", "dblclick", "clickCell", "toggleExpand", "toggleCheckbox"]);
39
+ const slots = useSlots();
40
+ const attrs = useAttrs();
28
41
 
29
42
  const cellRef = useTemplateRef<HTMLDivElement[]>("cell");
30
- const toggleWrapperRef = useTemplateRef<HTMLDivElement[]>("toggle-wrapper");
31
43
 
32
- useMutationObserver(cellRef, setCellTitle, {
33
- subtree: true,
34
- childList: true,
35
- characterData: true,
36
- attributes: false,
37
- });
44
+ if (props.textEllipsis) {
45
+ useMutationObserver(cellRef, setCellTitle, {
46
+ subtree: true,
47
+ childList: true,
48
+ characterData: true,
49
+ attributes: false,
50
+ });
51
+ }
38
52
 
39
53
  const toggleIconConfig = computed(() => {
40
54
  const nestedRow = props.row?.row;
@@ -63,17 +77,6 @@ function getToggleIconName() {
63
77
  : props.config?.defaults?.expandIcon;
64
78
  }
65
79
 
66
- function getIconWidth() {
67
- const icon = document.querySelector(`[data-row-toggle-icon='${props.row.id}']`);
68
- const currentWrapperWidth = toggleWrapperRef.value?.at(0)?.getBoundingClientRect()?.width || 0;
69
-
70
- if (icon) {
71
- return `${icon.getBoundingClientRect().width / PX_IN_REM}rem`;
72
- }
73
-
74
- return `${currentWrapperWidth / PX_IN_REM || 1}rem`;
75
- }
76
-
77
80
  function getCellClasses(row: Row, key: string) {
78
81
  const isCellData = typeof row[key] === "object" && row[key] !== null && "class" in row[key];
79
82
  const cell = row[key] as CellObject;
@@ -107,20 +110,6 @@ function getNestedCheckboxShift() {
107
110
  return { transform: `translateX(${props.nestedLevel * LAST_NESTED_ROW_SHIFT_REM}rem)` };
108
111
  }
109
112
 
110
- function onClick(row: Row) {
111
- emit("click", row);
112
- }
113
-
114
- function onDoubleClick(row: Row) {
115
- const selection = window.getSelection();
116
-
117
- if (selection) {
118
- selection.removeAllRanges();
119
- }
120
-
121
- emit("dblclick", row);
122
- }
123
-
124
113
  function setCellTitle(mutations: MutationRecord[]) {
125
114
  mutations.forEach((mutation) => {
126
115
  const { target } = mutation;
@@ -145,10 +134,6 @@ function setElementTitle(element: HTMLElement) {
145
134
  }
146
135
  }
147
136
 
148
- function onClickCell(cell: unknown | string | number, row: Row, key: string | number) {
149
- emit("clickCell", cell, row, key);
150
- }
151
-
152
137
  function getRowClasses(row: Row) {
153
138
  const rowClasses = row?.class || "";
154
139
 
@@ -159,14 +144,6 @@ function getRowAttrs() {
159
144
  return props.isChecked ? props.attrs.bodyRowCheckedAttrs.value : props.attrs.bodyRowAttrs.value;
160
145
  }
161
146
 
162
- function onToggleExpand(row: Row) {
163
- emit("toggleExpand", row);
164
- }
165
-
166
- function onInputCheckbox(row: Row) {
167
- emit("toggleCheckbox", row);
168
- }
169
-
170
147
  function getStickyColumnStyle(column: ColumnObject) {
171
148
  const position = props.columnPositions.get(column.key);
172
149
 
@@ -195,122 +172,349 @@ function getStickyColumnClass(column: ColumnObject) {
195
172
  return "";
196
173
  }
197
174
 
175
+ function isCellSearchMatch(key: string): boolean {
176
+ return Boolean(props.searchMatchColumns?.has(key));
177
+ }
178
+
179
+ function isCellActiveSearchMatch(key: string): boolean {
180
+ return props.activeSearchMatchColumn === key;
181
+ }
182
+
183
+ function getSearchMatchCellClass(key: string): string {
184
+ if (!isCellSearchMatch(key)) return "";
185
+
186
+ return isCellActiveSearchMatch(key)
187
+ ? (props.attrs.bodyCellSearchMatchActiveAttrs.value.class as string)
188
+ : (props.attrs.bodyCellSearchMatchAttrs.value.class as string);
189
+ }
190
+
191
+ function getHighlightedHtml(value: Cell, key: string): string {
192
+ const text = String(formatCellValue(value));
193
+ const query = props.search;
194
+
195
+ if (!query || !isCellSearchMatch(key)) return escapeHtml(text);
196
+
197
+ const isActive = isCellActiveSearchMatch(key);
198
+ const matchClass = isActive
199
+ ? (props.attrs.bodyCellSearchMatchTextActiveAttrs.value.class as string)
200
+ : (props.attrs.bodyCellSearchMatchTextAttrs.value.class as string);
201
+
202
+ const lowerText = text.toLowerCase();
203
+ const lowerQuery = query.toLowerCase();
204
+ const parts: string[] = [];
205
+ let lastIndex = 0;
206
+
207
+ let pos = lowerText.indexOf(lowerQuery);
208
+
209
+ while (~pos) {
210
+ if (pos > lastIndex) {
211
+ parts.push(escapeHtml(text.slice(lastIndex, pos)));
212
+ }
213
+
214
+ parts.push(
215
+ `<mark class="${matchClass}">${escapeHtml(text.slice(pos, pos + query.length))}</mark>`,
216
+ );
217
+ lastIndex = pos + query.length;
218
+ pos = lowerText.indexOf(lowerQuery, lastIndex);
219
+ }
220
+
221
+ if (lastIndex < text.length) {
222
+ parts.push(escapeHtml(text.slice(lastIndex)));
223
+ }
224
+
225
+ return parts.join("");
226
+ }
227
+
228
+ function escapeHtml(text: string): string {
229
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
230
+ }
231
+
232
+ function isNestedFirstCell(index: number): boolean {
233
+ return (Boolean(props.row.row) || Boolean(props.nestedLevel)) && index === 0;
234
+ }
235
+
236
+ function getRowIndex(): number {
237
+ return props.rowIndex ?? 0;
238
+ }
239
+
240
+ function isSlotContentEmpty(content: unknown): boolean {
241
+ const toArray = (arg: unknown) => {
242
+ return Array.isArray(arg) ? arg : [arg];
243
+ };
244
+
245
+ if (content === null || content === undefined) return true;
246
+
247
+ if (typeof content === "boolean" || typeof content === "number") {
248
+ return false;
249
+ }
250
+
251
+ if (typeof content === "string") {
252
+ return content.length === 0;
253
+ }
254
+
255
+ return toArray(content).every((node) => {
256
+ if (node === null || node === undefined) return true;
257
+
258
+ if (typeof node === "boolean" || typeof node === "number") {
259
+ return false;
260
+ }
261
+
262
+ if (typeof node === "string") {
263
+ return node.length === 0;
264
+ }
265
+
266
+ const vnode = node as VNode;
267
+
268
+ return (
269
+ vnode.type === Comment ||
270
+ (vnode.type === Text && !vnode.children?.length) ||
271
+ (vnode.type === Fragment && !vnode.children?.length)
272
+ );
273
+ });
274
+ }
275
+
276
+ function resolveSlotContent(slot: Slot | undefined, slotParams: Record<string, unknown>) {
277
+ if (!slot) return null;
278
+
279
+ const content = slot(slotParams);
280
+
281
+ return isSlotContentEmpty(content) ? null : content;
282
+ }
283
+
284
+ function shouldRenderCellWrapper(row: Row, key: string): boolean {
285
+ return Boolean(
286
+ props.textEllipsis ||
287
+ (props.search && isCellSearchMatch(key)) ||
288
+ getCellContentClass(row, String(key)),
289
+ );
290
+ }
291
+
292
+ function renderCellContent(value: Cell, key: string, cellIndex: number): VNode | VNode[] | string {
293
+ const keyStr = String(key);
294
+
295
+ const slotContent = resolveSlotContent(slots[`cell-${key}`], {
296
+ value,
297
+ row: props.row,
298
+ index: getRowIndex(),
299
+ cellIndex,
300
+ });
301
+
302
+ // Check if slot exists
303
+ if (slotContent) {
304
+ return slotContent as VNode | VNode[] | string;
305
+ }
306
+
307
+ // Render cell wrapper with highlighted HTML
308
+ if (shouldRenderCellWrapper(props.row, keyStr)) {
309
+ return h("div", {
310
+ ref: cellRef,
311
+ ...props.attrs.bodyCellContentAttrs.value,
312
+ class: cx([
313
+ props.attrs.bodyCellContentAttrs.value.class,
314
+ getCellContentClass(props.row, keyStr),
315
+ ]),
316
+ innerHTML: getHighlightedHtml(value, keyStr),
317
+ });
318
+ }
319
+
320
+ // Render plain text
321
+ return formatCellValue(value);
322
+ }
323
+
324
+ function renderNestedFirstCell(value: Cell, key: string, cellIndex: number): VNode {
325
+ const keyStr = String(key);
326
+
327
+ const expandSlotContent = resolveSlotContent(slots.expand, {
328
+ index: getRowIndex(),
329
+ row: props.row,
330
+ expanded: props.isExpanded,
331
+ });
332
+
333
+ const toggleIconNode = h(UIcon, {
334
+ size: "xs",
335
+ interactive: true,
336
+ name: getToggleIconName(),
337
+ color: "primary",
338
+ ...toggleIconConfig.value,
339
+ });
340
+
341
+ const toggleWrapperNode = h(
342
+ "div",
343
+ {
344
+ ...props.attrs.bodyCellNestedIconWrapperAttrs.value,
345
+ },
346
+ [toggleIconNode],
347
+ );
348
+
349
+ const toggleIconWrapperNode = h(
350
+ "div",
351
+ {
352
+ "data-row-toggle-icon": props.row.id,
353
+ "data-expand-icon": props.row.id,
354
+ onDblclick: (e: Event) => e.stopPropagation(),
355
+ },
356
+ expandSlotContent || [toggleWrapperNode],
357
+ );
358
+
359
+ const cellSlotContent = resolveSlotContent(slots[`cell-${key}`], {
360
+ value,
361
+ row: props.row,
362
+ index: getRowIndex(),
363
+ cellIndex,
364
+ });
365
+
366
+ return h(
367
+ "div",
368
+ {
369
+ style: getNestedShift(),
370
+ ...props.attrs.bodyCellNestedAttrs.value,
371
+ },
372
+ [
373
+ props.row.row ? toggleIconWrapperNode : null,
374
+ // Cell content
375
+ ...(() => {
376
+ if (cellSlotContent) {
377
+ return Array.isArray(cellSlotContent) ? cellSlotContent : [cellSlotContent];
378
+ }
379
+
380
+ if (shouldRenderCellWrapper(props.row, keyStr)) {
381
+ return [
382
+ h("div", {
383
+ ref: cellRef,
384
+ ...props.attrs.bodyCellContentAttrs.value,
385
+ class: cx([
386
+ props.attrs.bodyCellContentAttrs.value.class,
387
+ getCellContentClass(props.row, keyStr),
388
+ ]),
389
+ "data-test": getDataTest(`${key}-cell`),
390
+ innerHTML: getHighlightedHtml(value, keyStr),
391
+ }),
392
+ ];
393
+ }
394
+
395
+ return [formatCellValue(value)];
396
+ })(),
397
+ ].filter(Boolean),
398
+ );
399
+ }
400
+
401
+ function renderTableCell(value: Cell, key: string, index: number): VNode {
402
+ const keyStr = String(key);
403
+
404
+ const nestedCellNode = isNestedFirstCell(index)
405
+ ? renderNestedFirstCell(value, key, index)
406
+ : renderCellContent(value, key, index);
407
+
408
+ const cellChildren = Array.isArray(nestedCellNode) ? nestedCellNode : [nestedCellNode];
409
+
410
+ return h(
411
+ "td",
412
+ {
413
+ key: index,
414
+ ...props.attrs.bodyCellBaseAttrs.value,
415
+ class: cx([
416
+ props.attrs.bodyCellBaseAttrs.value.class,
417
+ props.columns[index].tdClass,
418
+ getCellClasses(props.row, keyStr),
419
+ getStickyColumnClass(props.columns[index]),
420
+ getSearchMatchCellClass(keyStr),
421
+ ]),
422
+ style: getStickyColumnStyle(props.columns[index]),
423
+ "data-cell-key": key,
424
+ "data-test": getDataTest(`${key}-cell`),
425
+ },
426
+ cellChildren,
427
+ );
428
+ }
429
+
430
+ function renderCheckboxCell(): VNode | null {
431
+ if (!props.selectable) return null;
432
+
433
+ const checkboxNode = h(UCheckbox, {
434
+ modelValue: props.isChecked,
435
+ size: "md",
436
+ ...props.attrs.bodyCheckboxAttrs.value,
437
+ "data-id": props.row.id,
438
+ "data-checkbox-id": props.row.id,
439
+ "data-test": getDataTest("body-checkbox"),
440
+ });
441
+
442
+ return h(
443
+ "td",
444
+ {
445
+ ...props.attrs.bodyCellCheckboxAttrs.value,
446
+ "data-checkbox-id": props.row.id,
447
+ class: cx([
448
+ props.attrs.bodyCellCheckboxAttrs.value.class,
449
+ props.columns[0]?.sticky === StickySide.Left
450
+ ? props.attrs.bodyCellStickyLeftAttrs.value.class
451
+ : "",
452
+ ]),
453
+ style: {
454
+ ...getNestedCheckboxShift(),
455
+ ...(props.columns[0]?.sticky === StickySide.Left ? { left: "0" } : {}),
456
+ },
457
+ onDblclick: (e: Event) => e.stopPropagation(),
458
+ },
459
+ [checkboxNode],
460
+ );
461
+ }
462
+
463
+ function renderMainRow(): VNode {
464
+ const cells = Object.entries(mapRowColumns(props.row, props.columns)).map(
465
+ ([key, value], index) => {
466
+ return renderTableCell(value, key, index);
467
+ },
468
+ );
469
+
470
+ return h(
471
+ "tr",
472
+ {
473
+ ...attrs,
474
+ ...getRowAttrs(),
475
+ class: cx([getRowAttrs().class, getRowClasses(props.row)]),
476
+ style: { display: props.isVisible ? "" : "none" },
477
+ },
478
+ [renderCheckboxCell(), ...cells].filter(Boolean),
479
+ );
480
+ }
481
+
482
+ function renderNestedRow(nestedRowSlotContent: VNode[]): VNode {
483
+ const tdNode = h(
484
+ "td",
485
+ { colspan: props.columns.length + Number(props.selectable) },
486
+ nestedRowSlotContent,
487
+ );
488
+
489
+ return h(
490
+ "tr",
491
+ {
492
+ class: props.row.class,
493
+ style: { display: props.isVisible ? "" : "none" },
494
+ },
495
+ [tdNode],
496
+ );
497
+ }
498
+
499
+ function renderRows(): VNode[] {
500
+ if (props.row.parentRowId) {
501
+ const nestedRowSlotContent = resolveSlotContent(slots["nested-row"], {
502
+ index: getRowIndex(),
503
+ row: props.row,
504
+ nestedLevel: props.nestedLevel,
505
+ });
506
+
507
+ if (nestedRowSlotContent) {
508
+ return [renderNestedRow(nestedRowSlotContent)];
509
+ }
510
+ }
511
+
512
+ return [renderMainRow()];
513
+ }
514
+
198
515
  const { getDataTest } = useUI<Config>(defaultConfig);
199
516
  </script>
200
517
 
201
518
  <template>
202
- <tr
203
- v-if="!row.parentRowId || !hasSlotContent($slots['nested-row'])"
204
- v-bind="{ ...$attrs, ...getRowAttrs() }"
205
- :class="cx([getRowAttrs().class, getRowClasses(row)])"
206
- @click="onClick(props.row)"
207
- @dblclick="onDoubleClick(props.row)"
208
- >
209
- <td
210
- v-if="selectable"
211
- :style="{
212
- ...getNestedCheckboxShift(),
213
- ...(columns[0]?.sticky === StickySide.Left ? { left: '0' } : {}),
214
- }"
215
- v-bind="attrs.bodyCellCheckboxAttrs.value"
216
- :class="
217
- cx([
218
- attrs.bodyCellCheckboxAttrs.value.class,
219
- columns[0]?.sticky === StickySide.Left ? attrs.bodyCellStickyLeftAttrs.value.class : '',
220
- ])
221
- "
222
- @click.stop
223
- @dblclick.stop
224
- >
225
- <UCheckbox
226
- :model-value="isChecked"
227
- size="md"
228
- v-bind="attrs.bodyCheckboxAttrs.value"
229
- :data-id="row.id"
230
- :data-test="getDataTest('body-checkbox')"
231
- @input="onInputCheckbox(row)"
232
- />
233
- </td>
234
-
235
- <td
236
- v-for="(value, key, index) in mapRowColumns(row, columns)"
237
- :key="index"
238
- v-bind="attrs.bodyCellBaseAttrs.value"
239
- :class="
240
- cx([
241
- columns[index].tdClass,
242
- getCellClasses(row, String(key)),
243
- getStickyColumnClass(columns[index]),
244
- ])
245
- "
246
- :style="getStickyColumnStyle(columns[index])"
247
- @click="onClickCell(value, row, key)"
248
- >
249
- <div
250
- v-if="(row.row || nestedLevel) && index === 0"
251
- :style="getNestedShift()"
252
- v-bind="attrs.bodyCellNestedAttrs.value"
253
- >
254
- <div
255
- v-if="row.row"
256
- :data-row-toggle-icon="row.id"
257
- @dblclick.stop
258
- @click.stop="onToggleExpand(row)"
259
- >
260
- <slot name="expand" :row="row" :expanded="isExpanded">
261
- <div
262
- ref="toggle-wrapper"
263
- v-bind="attrs.bodyCellNestedIconWrapperAttrs.value"
264
- :style="{ width: getIconWidth() }"
265
- >
266
- <UIcon
267
- size="xs"
268
- interactive
269
- :name="getToggleIconName()"
270
- color="primary"
271
- v-bind="toggleIconConfig"
272
- />
273
- </div>
274
- </slot>
275
- </div>
276
- <slot :name="`cell-${key}`" :value="value" :row="row" :index="index">
277
- <div
278
- v-if="value"
279
- ref="cell"
280
- v-bind="attrs.bodyCellContentAttrs.value"
281
- :class="
282
- cx([attrs.bodyCellContentAttrs.value.class, getCellContentClass(row, String(key))])
283
- "
284
- :data-test="getDataTest(`${key}-cell`)"
285
- >
286
- {{ formatCellValue(value) }}
287
- </div>
288
- </slot>
289
- </div>
290
-
291
- <template v-else>
292
- <slot :name="`cell-${key}`" :value="value" :row="row" :index="index">
293
- <div
294
- v-bind="attrs.bodyCellContentAttrs.value"
295
- ref="cell"
296
- :class="
297
- cx([attrs.bodyCellContentAttrs.value.class, getCellContentClass(row, String(key))])
298
- "
299
- :data-test="getDataTest(`${key}-cell`)"
300
- >
301
- {{ formatCellValue(value) }}
302
- </div>
303
- </slot>
304
- </template>
305
- </td>
306
- </tr>
307
-
308
- <tr
309
- v-if="row.parentRowId && hasSlotContent($slots['nested-row'], { row, nestedLevel })"
310
- :class="row.class"
311
- >
312
- <td :colspan="columns.length + Number(selectable)">
313
- <slot name="nested-row" :row="row" :nested-level="nestedLevel" />
314
- </td>
315
- </tr>
519
+ <component :is="renderRows" />
316
520
  </template>
@@ -29,19 +29,37 @@ export default /*tw*/ {
29
29
  stickyHeaderLoader: "{ULoaderProgress} absolute top-auto bottom-0",
30
30
  headerActionsCheckbox: "{UCheckbox}",
31
31
  headerActionsCounter: "{>headerCounterBase} -ml-1.5",
32
- tableWrapper: "border border-solid border-muted rounded-medium bg-default overflow-x-auto",
33
- table: "min-w-full border-none text-medium w-auto table-auto",
34
- header:
35
- "border-b border-muted [&>tr:first-child>*]:first:rounded-tl-medium [&>tr:last-child>*]:last:rounded-tr-medium relative",
32
+ tableWrapper: {
33
+ base: "border border-solid border-muted rounded-medium bg-default overflow-x-auto ",
34
+ variants: {
35
+ virtualScroll: {
36
+ true: "h-150 overflow-y-auto",
37
+ },
38
+ },
39
+ },
40
+ table: {
41
+ base: "min-w-full border-none text-medium w-auto table-auto border-separate border-spacing-0",
42
+ variants: {
43
+ virtualScroll: {
44
+ true: "table-layout-fixed w-full",
45
+ },
46
+ },
47
+ },
48
+ header: {
49
+ base: "[&>tr:first-child>*]:first:rounded-tl-medium [&>tr:last-child>*]:last:rounded-tr-medium relative",
50
+ },
36
51
  headerRow: "",
37
52
  beforeHeaderRow: "border border-solid border-muted",
38
53
  beforeHeaderCell: "{>headerCellBase}",
39
54
  headerCellBase: {
40
- base: "p-4 text-medium font-normal text-lifted text-left text-nowrap",
55
+ base: "border-b border-muted p-4 text-medium font-normal text-lifted text-left text-nowrap",
41
56
  variants: {
42
57
  compact: {
43
58
  true: "px-4 py-3 last:px-4 last:py-3 first:px-4 first:py-3",
44
59
  },
60
+ virtualScroll: {
61
+ true: "sticky top-0 z-10 bg-default",
62
+ },
45
63
  },
46
64
  },
47
65
  headerCellSticky: "sticky z-20 bg-default",
@@ -79,6 +97,10 @@ export default /*tw*/ {
79
97
  bodyCellNestedExpandIcon: "{UIcon}",
80
98
  bodyCellNestedCollapseIcon: "{UIcon}",
81
99
  bodyCheckbox: "{UCheckbox}",
100
+ bodyCellSearchMatch: "bg-warning/10",
101
+ bodyCellSearchMatchActive: "bg-warning/15",
102
+ bodyCellSearchMatchText: "bg-warning/50 rounded-xs",
103
+ bodyCellSearchMatchTextActive: "bg-warning rounded-xs",
82
104
  bodyDateDivider: {
83
105
  base: "{UDivider} py-2",
84
106
  label: "py-0 leading-none",
@@ -116,12 +138,18 @@ export default /*tw*/ {
116
138
  },
117
139
  defaults: {
118
140
  emptyCellLabel: "—",
141
+ rowHeight: 40,
142
+ bufferSize: 10,
143
+ virtualScroll: false,
119
144
  compact: false,
120
145
  selectable: false,
121
146
  dateDivider: false,
122
147
  stickyHeader: false,
123
148
  stickyFooter: false,
124
149
  loading: false,
150
+ textEllipsis: false,
151
+ search: "",
152
+ searchMatch: -1,
125
153
  /* icons */
126
154
  expandIcon: "add",
127
155
  collapseIcon: "remove",