@vueye-table/headless 3.0.0-alpha.8 → 3.0.0-alpha.9

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/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useVirtualColumns, useVirtualRows } from "@vueye-table/vue";
2
2
  import { Fragment, computed, defineComponent, h, inject, nextTick, onMounted, onScopeDispose, provide, ref, shallowRef, useId, watch } from "vue";
3
+ import { getRowItemKey, gridCommand } from "@vueye-table/core";
3
4
 
4
5
  //#region src/shared.ts
5
6
  /** The element a component renders. Every headless component accepts `as`. */
@@ -76,14 +77,16 @@ const DataTableSelectRow = defineComponent({
76
77
  setup(props, { slots }) {
77
78
  const table = injectDataTable("<DataTableSelectRow>");
78
79
  return () => {
79
- const selected = table.isSelected(props.row.key);
80
+ const selected = table.tree ? props.row.selection === "all" : table.isSelected(props.row.key);
81
+ const indeterminate = table.tree && props.row.selection === "some";
80
82
  const toggle = () => table.toggleRow(props.row.key);
81
83
  return slots.default?.({
82
84
  selected,
85
+ indeterminate,
83
86
  toggle
84
87
  }) ?? checkbox({
85
88
  checked: selected,
86
- indeterminate: false,
89
+ indeterminate,
87
90
  label: props.label,
88
91
  disabled: table.selectionMode === "none",
89
92
  onChange: toggle
@@ -262,14 +265,16 @@ const DataTableStatus = defineComponent({
262
265
  loadError: table.loadError,
263
266
  canLoadMore: table.canLoadMore,
264
267
  retry: () => table.retry(),
265
- loadNext: () => table.loadNext()
268
+ loadNext: () => table.loadNext(),
269
+ loadingChildrenCount: table.tree ? table.processedRows.filter((row) => row.childStatus === "loading").length : 0,
270
+ childErrorCount: table.tree ? table.processedRows.filter((row) => row.childStatus === "error").length : 0
266
271
  };
267
272
  const count = `Loaded ${table.loadedRowCount.toLocaleString()} ${table.loadedRowCount === 1 ? "row" : "rows"}`;
268
273
  const text = table.loadingMode ? table.loadState === "error" ? `${count}. Could not load rows. Retry loading.` : table.loadState === "aborted" ? `${count}. Loading stopped.` : table.loadState === "loading" || table.loadState === "streaming" ? `${count}…` : `${count}${table.loadState === "done" ? ". All rows loaded." : "."}` : table.rowCount === 0 ? "No rows" : `${table.pageStart}–${table.pageEnd} of ${table.rowCount} rows` + (table.selectedCount > 0 ? `, ${table.selectedCount} selected` : "");
269
274
  return h(props.as, {
270
275
  role: "status",
271
276
  "aria-live": "polite"
272
- }, slots.default?.(slotProps) ?? text);
277
+ }, slots.default?.(slotProps) ?? text + (slotProps.loadingChildrenCount ? " Loading children…" : "") + (slotProps.childErrorCount ? " Could not load children. Retry children in the affected row." : ""));
273
278
  };
274
279
  }
275
280
  });
@@ -556,6 +561,269 @@ function virtualRow(binding, row) {
556
561
  return binding.rowItems.find((item) => item.renderItem.kind === "row" && item.renderItem.row.key === row.key);
557
562
  }
558
563
 
564
+ //#endregion
565
+ //#region src/hierarchy.ts
566
+ const hierarchyProps = {
567
+ treeColumn: {
568
+ type: String,
569
+ default: void 0
570
+ },
571
+ keepAliveDetail: {
572
+ type: Boolean,
573
+ default: false
574
+ }
575
+ };
576
+ const hierarchyKey = Symbol("vueye-table-hierarchy");
577
+ function provideHierarchy(table, props) {
578
+ const focus = shallowRef();
579
+ const focusKey = computed(() => table.rows.some((row) => row.key === focus.value) ? focus.value : table.rows[0]?.key);
580
+ const context = {
581
+ id: `vt-tree-${useId()}`,
582
+ table,
583
+ get treeColumn() {
584
+ return props.treeColumn;
585
+ },
586
+ get keepAliveDetail() {
587
+ return props.keepAliveDetail;
588
+ },
589
+ focus,
590
+ get focusKey() {
591
+ return focusKey.value;
592
+ }
593
+ };
594
+ provide(hierarchyKey, context);
595
+ return context;
596
+ }
597
+ function injectHierarchy() {
598
+ return inject(hierarchyKey, void 0);
599
+ }
600
+ function resolveTreeColumn(table, column) {
601
+ return table.columns.find((candidate) => candidate.id === column)?.id ?? table.columns[0]?.id;
602
+ }
603
+ function itemId(context, row, detail = false) {
604
+ return context ? `${context.id}-${encodeURIComponent(getRowItemKey(row.key, detail ? "detail" : "row"))}` : void 0;
605
+ }
606
+ const siblingCache = /* @__PURE__ */ new WeakMap();
607
+ function hierarchyRowAttrs(table, row, context) {
608
+ if (!table.tree) return {};
609
+ let positions = siblingCache.get(table.processedRows);
610
+ if (!positions) {
611
+ const groups = /* @__PURE__ */ new Map();
612
+ for (const candidate of table.processedRows) {
613
+ const group = groups.get(candidate.parentKey) ?? [];
614
+ group.push(candidate);
615
+ groups.set(candidate.parentKey, group);
616
+ }
617
+ const indices = new Map(table.processedRows.map((candidate, index) => [candidate.key, index]));
618
+ const result = /* @__PURE__ */ new Map();
619
+ for (const group of groups.values()) group.forEach((candidate, index) => result.set(candidate.key, {
620
+ position: index + 1,
621
+ size: group.length,
622
+ index: indices.get(candidate.key)
623
+ }));
624
+ positions = result;
625
+ siblingCache.set(table.processedRows, result);
626
+ }
627
+ const position = positions.get(row.key);
628
+ return {
629
+ id: itemId(context, row),
630
+ "data-tree-key": getRowItemKey(row.key),
631
+ "aria-level": row.depth + 1,
632
+ "aria-expanded": row.canExpand ? String(row.isExpanded) : void 0,
633
+ "aria-setsize": position?.size,
634
+ "aria-posinset": position?.position,
635
+ "aria-rowindex": position ? position.index + 2 : void 0,
636
+ "aria-busy": row.childStatus === "loading" ? "true" : void 0
637
+ };
638
+ }
639
+ /** Row-focused table navigation; cell-focused grids use the same pure core commands. */
640
+ function treeKeydown(event, context, virtual) {
641
+ const table = context.table;
642
+ const target = event.target;
643
+ if (!table.tree || !target?.closest) return;
644
+ if (target.closest("input, textarea, select, [contenteditable=true]") || target.closest("button") && !target.closest("[data-expand-toggle]")) return;
645
+ const key = target.closest("tr")?.getAttribute("data-tree-key");
646
+ const index = table.rows.findIndex((row) => getRowItemKey(row.key) === key);
647
+ const row = table.rows[index];
648
+ if (!row) return;
649
+ const command = gridCommand(event, {
650
+ canExpand: row.canExpand,
651
+ expanded: row.isExpanded
652
+ });
653
+ let next = row;
654
+ if (command?.type === "tree") {
655
+ if (command.action === "expand" || command.action === "collapse") table.toggleExpanded(row.key, command.action === "expand");
656
+ else if (command.action === "siblings") for (const sibling of table.processedRows.filter((candidate) => candidate.parentKey === row.parentKey && candidate.canExpand)) table.toggleExpanded(sibling.key, true);
657
+ else if (command.action === "child") next = table.rows.find((candidate) => candidate.parentKey === row.key) ?? row;
658
+ else next = table.rows.find((candidate) => candidate.key === row.parentKey) ?? row;
659
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp") next = table.rows[index + (event.key === "ArrowDown" ? 1 : -1)] ?? row;
660
+ else if (event.key === "Home" || event.key === "End") next = table.rows[event.key === "Home" ? 0 : table.rows.length - 1] ?? row;
661
+ else return;
662
+ event.preventDefault();
663
+ context.focus.value = next.key;
664
+ virtual?.rows.scrollToKey(next.key);
665
+ const id = itemId(context, next);
666
+ nextTick(() => target.closest("table")?.ownerDocument.getElementById(id ?? "")?.focus());
667
+ }
668
+ const expandToggleProps = {
669
+ row: rowProp,
670
+ controls: {
671
+ type: String,
672
+ default: void 0
673
+ },
674
+ expandLabel: {
675
+ type: String,
676
+ default: "Expand row"
677
+ },
678
+ collapseLabel: {
679
+ type: String,
680
+ default: "Collapse row"
681
+ }
682
+ };
683
+ const treeCellProps = {
684
+ row: rowProp,
685
+ as: asProp("div")
686
+ };
687
+ const DataTableExpandToggle = defineComponent({
688
+ name: "DataTableExpandToggle",
689
+ props: expandToggleProps,
690
+ slots: Object,
691
+ setup(props, { slots }) {
692
+ const table = injectDataTable("<DataTableExpandToggle>");
693
+ const context = injectHierarchy();
694
+ const grid = injectDataGrid();
695
+ return () => props.row.canExpand ? h("button", {
696
+ type: "button",
697
+ "data-expand-toggle": "",
698
+ tabindex: table.tree ? -1 : void 0,
699
+ "aria-expanded": String(props.row.isExpanded),
700
+ "aria-controls": props.controls ?? (!table.tree ? itemId(context, props.row, true) : props.row.isExpanded ? table.rows.filter((row) => row.parentKey === props.row.key).map((row) => itemId(context, row)).filter(Boolean).join(" ") || void 0 : void 0),
701
+ "aria-label": props.row.isExpanded ? props.collapseLabel : props.expandLabel,
702
+ onMousedown: (event) => event.stopPropagation(),
703
+ onDblclick: (event) => event.stopPropagation(),
704
+ onClick: (event) => {
705
+ event.stopPropagation();
706
+ if (grid?.table === table) grid.focusCell({
707
+ row: table.rows.indexOf(props.row),
708
+ column: Math.max(0, table.columns.findIndex((column) => column.id === resolveTreeColumn(table, context?.treeColumn)))
709
+ });
710
+ table.toggleExpanded(props.row.key);
711
+ if (grid?.table === table) nextTick(() => event.target.closest("table")?.focus());
712
+ }
713
+ }, slots.default?.({
714
+ row: props.row,
715
+ expanded: props.row.isExpanded
716
+ }) ?? (props.row.isExpanded ? props.collapseLabel : props.expandLabel)) : null;
717
+ }
718
+ });
719
+ const DataTableTreeCell = defineComponent({
720
+ name: "DataTableTreeCell",
721
+ props: treeCellProps,
722
+ slots: Object,
723
+ setup(props, { slots }) {
724
+ const table = injectDataTable("<DataTableTreeCell>");
725
+ const context = injectHierarchy();
726
+ const grid = injectDataGrid();
727
+ return () => h(props.as, {
728
+ class: "vt-tree-cell",
729
+ "data-tree-cell": "",
730
+ style: { paddingInlineStart: `calc(var(--vt-tree-indent, 1.25rem) * ${props.row.depth})` }
731
+ }, [
732
+ h("span", { class: "vt-tree-toggle" }, slots.toggle?.({ row: props.row }) ?? h(DataTableExpandToggle, { row: props.row })),
733
+ h("span", { class: "vt-tree-label" }, [slots.default?.()]),
734
+ props.row.childStatus === "loading" ? h("span", { class: "vt-tree-loading" }, "Loading children…") : null,
735
+ props.row.childStatus === "error" ? h("span", { class: "vt-tree-error" }, ["Could not load children. ", h("button", {
736
+ type: "button",
737
+ class: "vt-tree-retry",
738
+ onMousedown: (event) => event.stopPropagation(),
739
+ onClick: (event) => {
740
+ event.stopPropagation();
741
+ const element = event.currentTarget.closest("table");
742
+ if (grid?.table === table) grid.focusCell({
743
+ row: table.rows.indexOf(props.row),
744
+ column: Math.max(0, table.columns.findIndex((column) => column.id === resolveTreeColumn(table, context?.treeColumn)))
745
+ });
746
+ table.toggleExpanded(props.row.key, true);
747
+ nextTick(() => {
748
+ if (grid?.table === table) element?.focus();
749
+ else element?.ownerDocument.getElementById(itemId(context, props.row) ?? "")?.focus();
750
+ });
751
+ }
752
+ }, "Retry children")]) : null
753
+ ]);
754
+ }
755
+ });
756
+ /** One full-width detail; retained collapsed content remains hidden and unmeasured. */
757
+ const DataTableDetailRow = defineComponent({
758
+ name: "DataTableDetailRow",
759
+ props: {
760
+ row: rowProp,
761
+ colspan: {
762
+ type: Number,
763
+ default: void 0
764
+ },
765
+ hidden: {
766
+ type: Boolean,
767
+ default: false
768
+ },
769
+ keepAlive: {
770
+ type: Boolean,
771
+ default: void 0
772
+ }
773
+ },
774
+ slots: Object,
775
+ setup(props, { slots }) {
776
+ const table = injectDataTable("<DataTableDetailRow>");
777
+ const context = injectHierarchy();
778
+ const virtual = injectVirtual();
779
+ let opened = false;
780
+ return () => {
781
+ if (props.row.isExpanded && !props.hidden) opened = true;
782
+ if (!opened || (!props.row.isExpanded || props.hidden) && !(props.keepAlive ?? context?.keepAliveDetail)) return null;
783
+ const hidden = props.hidden || !props.row.isExpanded;
784
+ return h("tr", {
785
+ id: itemId(context, props.row, true),
786
+ "data-detail": "",
787
+ hidden,
788
+ ...virtual && !hidden ? { ref: (target) => virtual.rows.measureElement(target, getRowItemKey(props.row.key, "detail")) } : {}
789
+ }, [h("td", {
790
+ colspan: Math.max(1, props.colspan ?? table.columns.length),
791
+ class: "vt-detail-cell"
792
+ }, [slots.default?.({ row: props.row })])]);
793
+ };
794
+ }
795
+ });
796
+ function createDetails(table, slot, colspan) {
797
+ const context = injectHierarchy();
798
+ const visited = /* @__PURE__ */ new Set();
799
+ const render = (row, rowIndex, hidden = false) => {
800
+ if (!hidden && context?.keepAliveDetail) visited.add(row.key);
801
+ return h(DataTableDetailRow, {
802
+ key: getRowItemKey(row.key, "detail"),
803
+ row,
804
+ colspan: colspan(),
805
+ hidden
806
+ }, { default: () => slot()?.({
807
+ row,
808
+ rowIndex
809
+ }) });
810
+ };
811
+ return {
812
+ render,
813
+ retained(visible) {
814
+ if (!context?.keepAliveDetail) return [];
815
+ return [...visited].flatMap((key) => {
816
+ const index = table.rows.findIndex((row) => row.key === key);
817
+ if (index < 0) {
818
+ visited.delete(key);
819
+ return [];
820
+ }
821
+ return visible.has(key) ? [] : [render(table.rows[index], index, true)];
822
+ });
823
+ }
824
+ };
825
+ }
826
+
559
827
  //#endregion
560
828
  //#region src/table.ts
561
829
  /**
@@ -571,16 +839,20 @@ const DataTableRoot = defineComponent({
571
839
  required: true
572
840
  },
573
841
  as: asProp("table"),
574
- ...virtualProps
842
+ ...virtualProps,
843
+ ...hierarchyProps
575
844
  },
576
845
  setup(props, { slots }) {
577
846
  provideDataTable(props.table);
847
+ const hierarchy = provideHierarchy(props.table, props);
578
848
  const element = shallowRef();
579
849
  const render = (virtual) => h(props.as, {
580
- "aria-rowcount": props.table.rowCount + 1,
850
+ role: props.table.tree ? "treegrid" : void 0,
851
+ "aria-rowcount": (props.table.tree ? props.table.processedRows.length : props.table.rowCount) + 1,
581
852
  "aria-colcount": props.table.columns.length,
582
853
  "aria-busy": props.table.loadState === "loading" || props.table.loadState === "streaming" ? "true" : void 0,
583
854
  "data-empty": flag(props.table.rowCount === 0),
855
+ onKeydown: (event) => treeKeydown(event, hierarchy, virtual),
584
856
  ...virtual ? {
585
857
  ref: element,
586
858
  "data-virtual": ""
@@ -729,20 +1001,28 @@ const DataTableBody = defineComponent({
729
1001
  setup(props, { slots }) {
730
1002
  const table = injectDataTable("<DataTableBody>");
731
1003
  const virtual = injectVirtual();
1004
+ const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length);
732
1005
  return () => {
733
1006
  if (props.virtual && !virtual) return h(VirtualDataTableBody, props, slots);
734
- if (virtual) return renderTableBody(table, virtual, props.as, props.colspan, slots);
1007
+ if (virtual) return renderTableBody(table, virtual, props.as, props.colspan, slots, details);
735
1008
  let content = slots.default?.({ rows: table.rows });
736
- if (!content) content = table.rows.length === 0 && slots.empty ? h("tr", { "data-empty": "" }, [h("td", { colspan: Math.max(1, table.columns.length) }, slots.empty())]) : table.rows.map((row) => h(DataTableRow, {
737
- key: row.key,
738
- row
739
- }));
1009
+ if (!content) content = table.rows.length === 0 && slots.empty ? h("tr", { "data-empty": "" }, [h("td", { colspan: Math.max(1, table.columns.length) }, slots.empty())]) : table.renderItems.map((item) => item.kind === "row" ? slots.row ? h(Fragment, { key: item.key }, [slots.row(item)]) : h(DataTableRow, {
1010
+ key: item.key,
1011
+ row: item.row,
1012
+ rowIndex: item.rowIndex
1013
+ }) : details.render(item.row, item.rowIndex));
1014
+ const visibleDetails = new Set(table.renderItems.filter((item) => item.kind === "detail").map((item) => item.row.key));
1015
+ const retained = slots.default ? [] : details.retained(visibleDetails);
740
1016
  const sentinel = loadSentinel(table, props.colspan ?? table.columns.length);
741
- return h(props.as, sentinel ? [content, sentinel] : content);
1017
+ return h(props.as, [
1018
+ ...Array.isArray(content) ? content : content ? [content] : [],
1019
+ ...retained,
1020
+ ...sentinel ? [sentinel] : []
1021
+ ]);
742
1022
  };
743
1023
  }
744
1024
  });
745
- function renderTableBody(table, virtual, as, colspan, slots, element) {
1025
+ function renderTableBody(table, virtual, as, colspan, slots, details, element) {
746
1026
  const width = colspan ?? table.columns.length;
747
1027
  const attrs = element ? { ref: element } : {};
748
1028
  const content = slots.default?.({ rows: virtual.rowItems.filter((item) => item.renderItem.kind === "row").map((item) => item.renderItem.row) });
@@ -753,15 +1033,15 @@ function renderTableBody(table, virtual, as, colspan, slots, element) {
753
1033
  rowSpacer(virtual.rows.paddingEnd, width, "gap-end"),
754
1034
  ...loadSentinel(table, width) ? [loadSentinel(table, width)] : []
755
1035
  ]);
756
- return h(as, attrs, [...renderRows(virtual, width, (item) => item.renderItem.kind === "row" ? h(DataTableRow, {
757
- key: item.key,
758
- row: item.renderItem.row,
759
- rowIndex: item.renderItem.rowIndex
760
- }) : h("tr", {
761
- key: item.key,
762
- "data-detail": "",
763
- ref: (target) => virtual.rows.measureElement(target, item.key)
764
- }, [h("td", { colspan: Math.max(1, width) }, slots.detail?.(item.renderItem))])), ...loadSentinel(table, width) ? [loadSentinel(table, width)] : []]);
1036
+ return h(as, attrs, [
1037
+ ...renderRows(virtual, width, (item) => item.renderItem.kind === "row" ? slots.row ? h(Fragment, { key: item.key }, [slots.row(item.renderItem)]) : h(DataTableRow, {
1038
+ key: item.key,
1039
+ row: item.renderItem.row,
1040
+ rowIndex: item.renderItem.rowIndex
1041
+ }) : details.render(item.renderItem.row, item.renderItem.rowIndex)),
1042
+ ...details.retained(new Set(virtual.rowItems.filter((item) => item.renderItem.kind === "detail").map((item) => item.renderItem.row.key))),
1043
+ ...loadSentinel(table, width) ? [loadSentinel(table, width)] : []
1044
+ ]);
765
1045
  }
766
1046
  const VirtualDataTableBody = defineComponent({
767
1047
  name: "DataTableVirtualBody",
@@ -778,7 +1058,8 @@ const VirtualDataTableBody = defineComponent({
778
1058
  const table = injectDataTable("<DataTableBody>");
779
1059
  const element = shallowRef();
780
1060
  const virtual = createComponentVirtual(table, virtualOptions(props), () => element.value?.closest("table"));
781
- return () => renderTableBody(table, virtual, props.as, props.colspan, slots, element);
1061
+ const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length);
1062
+ return () => renderTableBody(table, virtual, props.as, props.colspan, slots, details, element);
782
1063
  }
783
1064
  });
784
1065
  /** A body row; `aria-selected` and `data-selected` follow the selection. */
@@ -796,6 +1077,7 @@ const DataTableRow = defineComponent({
796
1077
  setup(props, { slots }) {
797
1078
  const table = injectDataTable("<DataTableRow>");
798
1079
  const virtual = injectVirtual();
1080
+ const hierarchy = injectHierarchy();
799
1081
  return () => {
800
1082
  const { row } = props;
801
1083
  const selected = table.isSelected(row.key);
@@ -806,6 +1088,11 @@ const DataTableRow = defineComponent({
806
1088
  "aria-selected": table.selectionMode === "none" ? void 0 : String(selected),
807
1089
  "data-selected": flag(selected),
808
1090
  "data-key": String(row.key),
1091
+ ...hierarchyRowAttrs(table, row, hierarchy),
1092
+ tabindex: table.tree ? (hierarchy?.focusKey ?? table.rows[0]?.key) === row.key ? 0 : -1 : void 0,
1093
+ onFocus: () => {
1094
+ if (hierarchy) hierarchy.focus.value = row.key;
1095
+ },
809
1096
  ...virtual && item ? {
810
1097
  ref: (element) => virtual.rows.measureElement(element, item.key),
811
1098
  "data-virtual-row": ""
@@ -829,22 +1116,29 @@ const DataTableCell = defineComponent({
829
1116
  props: {
830
1117
  row: rowProp,
831
1118
  column: columnProp,
832
- as: asProp("td")
1119
+ as: asProp("td"),
1120
+ tree: {
1121
+ type: Boolean,
1122
+ default: true
1123
+ }
833
1124
  },
834
1125
  setup(props, { slots }) {
1126
+ const table = injectDataTable("<DataTableCell>");
1127
+ const hierarchy = injectHierarchy();
835
1128
  return () => {
836
1129
  const { row, column } = props;
837
1130
  const value = row.getValue(column.id);
838
1131
  const display = row.getDisplay(column.id);
839
- return h(props.as, {
840
- "data-column": column.id,
841
- "data-align": column.align
842
- }, slots.default?.({
1132
+ const content = () => slots.default?.({
843
1133
  row,
844
1134
  column,
845
1135
  value,
846
1136
  display
847
- }) ?? display);
1137
+ }) ?? display;
1138
+ return h(props.as, {
1139
+ "data-column": column.id,
1140
+ "data-align": column.align
1141
+ }, props.tree && table.tree && column.id === resolveTreeColumn(table, hierarchy?.treeColumn) ? h(DataTableTreeCell, { row }, { default: content }) : content());
848
1142
  };
849
1143
  }
850
1144
  });
@@ -893,6 +1187,7 @@ const DataGridRoot = defineComponent({
893
1187
  default: void 0
894
1188
  },
895
1189
  ...virtualProps,
1190
+ ...hierarchyProps,
896
1191
  /** Width reserved before data columns, for a row-number/header column. */
897
1192
  gutter: {
898
1193
  type: Number,
@@ -901,8 +1196,9 @@ const DataGridRoot = defineComponent({
901
1196
  },
902
1197
  setup(props, { slots, expose }) {
903
1198
  provideDataTable(props.table);
904
- const grid = useDataGrid(props.table);
1199
+ const grid = useDataGrid(props.table, { treeColumn: () => props.treeColumn });
905
1200
  provideDataGrid(grid);
1201
+ provideHierarchy(props.table, props);
906
1202
  const element = ref();
907
1203
  const id = `vt-grid-${useId()}`;
908
1204
  const focus = () => {
@@ -964,11 +1260,11 @@ const DataGridRoot = defineComponent({
964
1260
  return h(props.as, {
965
1261
  ref: element,
966
1262
  id,
967
- role: "grid",
1263
+ role: props.table.tree ? "treegrid" : "grid",
968
1264
  tabindex: 0,
969
1265
  "aria-label": props.label,
970
1266
  "aria-multiselectable": "true",
971
- "aria-rowcount": (binding ? props.table.rowCount : props.table.rows.length) + 1,
1267
+ "aria-rowcount": (props.table.tree ? props.table.processedRows.length : binding ? props.table.rowCount : props.table.rows.length) + 1,
972
1268
  "aria-colcount": props.table.columns.length,
973
1269
  "aria-busy": props.table.loadState === "loading" || props.table.loadState === "streaming" ? "true" : void 0,
974
1270
  "aria-activedescendant": focusPosition && !grid.editor ? cellId(id, focusPosition.row, focusPosition.column) : void 0,
@@ -1015,12 +1311,18 @@ const DataGridBody = defineComponent({
1015
1311
  colspan: {
1016
1312
  type: Number,
1017
1313
  default: void 0
1314
+ },
1315
+ treeCell: {
1316
+ type: Boolean,
1317
+ default: true
1018
1318
  }
1019
1319
  },
1020
1320
  setup(props, { slots }) {
1021
1321
  const table = injectDataTable("<DataGridBody>");
1022
1322
  const { grid } = useGrid("<DataGridBody>");
1023
1323
  const virtual = injectVirtual();
1324
+ const hierarchy = injectHierarchy();
1325
+ const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
1024
1326
  const element = ref();
1025
1327
  const render = (binding) => {
1026
1328
  if (binding && slots.default) {
@@ -1028,53 +1330,60 @@ const DataGridBody = defineComponent({
1028
1330
  const content = slots.default({ rows: binding.rowItems.filter((item) => item.renderItem.kind === "row").map((item) => item.renderItem.row) });
1029
1331
  return h(props.as, { ref: element }, sentinel ? [...content, sentinel] : content);
1030
1332
  }
1031
- if (binding) return h(props.as, { ref: element }, [...renderRows(binding, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0), (item) => {
1032
- if (item.renderItem.kind !== "row") return h("tr", {
1033
- key: item.key,
1034
- "aria-hidden": "true",
1035
- "data-detail": "",
1036
- style: { height: `${item.size}px` }
1037
- }, [h("td", {
1038
- colspan: Math.max(1, table.columns.length),
1039
- style: { padding: "0" }
1040
- })]);
1041
- const { row, rowIndex } = item.renderItem;
1042
- const columns = renderColumns(table.columns, binding, (column, columnIndex) => h(DataGridCell, {
1043
- key: column.id,
1044
- row,
1045
- column,
1046
- rowIndex,
1047
- columnIndex
1048
- }, slots.cell ? { default: (cellProps) => slots.cell?.(cellProps) } : void 0), "td");
1333
+ if (binding) return h(props.as, { ref: element }, [
1334
+ ...renderRows(binding, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0), (item) => {
1335
+ if (item.renderItem.kind !== "row") return details.render(item.renderItem.row, item.renderItem.rowIndex);
1336
+ const { row, rowIndex } = item.renderItem;
1337
+ const columns = renderColumns(table.columns, binding, (column, columnIndex) => h(DataGridCell, {
1338
+ key: column.id,
1339
+ row,
1340
+ column,
1341
+ rowIndex,
1342
+ columnIndex,
1343
+ tree: props.treeCell
1344
+ }, slots.cell ? { default: (cellProps) => slots.cell?.(cellProps) } : void 0), "td");
1345
+ return h("tr", {
1346
+ key: item.key,
1347
+ role: "row",
1348
+ "aria-rowindex": table.pageStart + rowIndex + 1,
1349
+ "data-key": String(row.key),
1350
+ ...hierarchyRowAttrs(table, row, hierarchy),
1351
+ "data-virtual-row": "",
1352
+ ref: (target) => binding.rows.measureElement(target, item.key)
1353
+ }, [slots.rowHeader?.({
1354
+ row,
1355
+ index: rowIndex
1356
+ }), ...columns]);
1357
+ }),
1358
+ ...details.retained(new Set(binding.rowItems.filter((item) => item.renderItem.kind === "detail").map((item) => item.renderItem.row.key))),
1359
+ ...loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0)) ? [loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0))] : []
1360
+ ]);
1361
+ const content = slots.default?.({ rows: table.rows }) ?? table.renderItems.map((item) => item.kind === "detail" ? details.render(item.row, item.rowIndex) : (() => {
1362
+ const { row, rowIndex } = item;
1049
1363
  return h("tr", {
1050
1364
  key: item.key,
1051
1365
  role: "row",
1052
- "aria-rowindex": table.pageStart + rowIndex + 1,
1366
+ "aria-rowindex": rowIndex + 2,
1053
1367
  "data-key": String(row.key),
1054
- "data-virtual-row": "",
1055
- ref: (target) => binding.rows.measureElement(target, item.key)
1368
+ ...hierarchyRowAttrs(table, row, hierarchy)
1056
1369
  }, [slots.rowHeader?.({
1057
1370
  row,
1058
1371
  index: rowIndex
1059
- }), ...columns]);
1060
- }), ...loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0)) ? [loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0))] : []]);
1061
- const content = slots.default?.({ rows: table.rows }) ?? table.rows.map((row, rowIndex) => h("tr", {
1062
- key: row.key,
1063
- role: "row",
1064
- "aria-rowindex": rowIndex + 2,
1065
- "data-key": String(row.key)
1066
- }, [slots.rowHeader?.({
1067
- row,
1068
- index: rowIndex
1069
- }), ...table.columns.map((column, columnIndex) => h(DataGridCell, {
1070
- key: column.id,
1071
- row,
1072
- column,
1073
- rowIndex,
1074
- columnIndex
1075
- }, slots.cell ? { default: (cellProps) => slots.cell?.(cellProps) } : void 0))]));
1372
+ }), ...table.columns.map((column, columnIndex) => h(DataGridCell, {
1373
+ key: column.id,
1374
+ row,
1375
+ column,
1376
+ rowIndex,
1377
+ columnIndex,
1378
+ tree: props.treeCell
1379
+ }, slots.cell ? { default: (cellProps) => slots.cell?.(cellProps) } : void 0))]);
1380
+ })());
1076
1381
  const sentinel = loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
1077
- return h(props.as, sentinel ? [...content, sentinel] : content);
1382
+ return h(props.as, [
1383
+ ...content,
1384
+ ...slots.default ? [] : details.retained(new Set(table.renderItems.filter((item) => item.kind === "detail").map((item) => item.row.key))),
1385
+ sentinel
1386
+ ]);
1078
1387
  };
1079
1388
  return () => props.virtual && !virtual ? h(VirtualContent, {
1080
1389
  table,
@@ -1142,6 +1451,10 @@ const DataGridCell = defineComponent({
1142
1451
  row: rowProp,
1143
1452
  column: columnProp,
1144
1453
  as: asProp("td"),
1454
+ tree: {
1455
+ type: Boolean,
1456
+ default: true
1457
+ },
1145
1458
  /** The row's position on the page. Found by searching the rows when left out. */
1146
1459
  rowIndex: {
1147
1460
  type: Number,
@@ -1157,6 +1470,7 @@ const DataGridCell = defineComponent({
1157
1470
  const table = injectDataTable("<DataGridCell>");
1158
1471
  const { grid, context } = useGrid("<DataGridCell>");
1159
1472
  const virtual = injectVirtual();
1473
+ const hierarchy = injectHierarchy();
1160
1474
  const element = ref();
1161
1475
  const position = () => ({
1162
1476
  row: props.rowIndex ?? table.rows.indexOf(props.row),
@@ -1182,13 +1496,16 @@ const DataGridCell = defineComponent({
1182
1496
  commit: (then) => grid.commitEdit(then),
1183
1497
  cancel: () => grid.cancelEdit()
1184
1498
  }) ?? h(DataGridEditor, { label: `Edit ${column.header}` });
1185
- else content = slots.default?.({
1186
- row,
1187
- column,
1188
- value,
1189
- display,
1190
- editable
1191
- }) ?? display;
1499
+ else {
1500
+ const children = () => slots.default?.({
1501
+ row,
1502
+ column,
1503
+ value,
1504
+ display,
1505
+ editable
1506
+ }) ?? display;
1507
+ content = props.tree && table.tree && column.id === resolveTreeColumn(table, hierarchy?.treeColumn) ? h(DataTableTreeCell, { row }, { default: children }) : children();
1508
+ }
1192
1509
  return h(props.as, {
1193
1510
  ref: element,
1194
1511
  id: cellId(context.id, at.row, at.column),
@@ -1221,5 +1538,5 @@ const DataGridCell = defineComponent({
1221
1538
  });
1222
1539
 
1223
1540
  //#endregion
1224
- export { DataGridBody, DataGridCell, DataGridRoot, DataTableBody, DataTableCaption, DataTableCell, DataTableColumnVisibility, DataTableEmpty, DataTableHeader, DataTableHeaderCell, DataTableHeaderRow, DataTableLoadMore, DataTablePageSize, DataTablePagination, DataTableRoot, DataTableRow, DataTableSearch, DataTableSelectAll, DataTableSelectRow, DataTableSortButton, DataTableStatus, DataTableViewport, DataTableVirtualColumns, cellId, columnStyle, injectVirtual as injectVirtualRenderer, virtualProps };
1541
+ export { DataGridBody, DataGridCell, DataGridRoot, DataTableBody, DataTableCaption, DataTableCell, DataTableColumnVisibility, DataTableDetailRow, DataTableEmpty, DataTableExpandToggle, DataTableHeader, DataTableHeaderCell, DataTableHeaderRow, DataTableLoadMore, DataTablePageSize, DataTablePagination, DataTableRoot, DataTableRow, DataTableSearch, DataTableSelectAll, DataTableSelectRow, DataTableSortButton, DataTableStatus, DataTableTreeCell, DataTableViewport, DataTableVirtualColumns, cellId, columnStyle, expandToggleProps, hierarchyProps, injectVirtual as injectVirtualRenderer, resolveTreeColumn, treeCellProps, virtualProps };
1225
1542
  //# sourceMappingURL=index.js.map