@sit-onyx/headless 0.7.1-dev-20260316075404 → 0.7.1
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,8 +1,8 @@
|
|
|
1
1
|
import { MaybeRefOrGetter, Ref } from 'vue';
|
|
2
2
|
type RequestLazyLoad = (colIndex: number, rowIndex: number) => Promise<void>;
|
|
3
3
|
type CellIdentifier = {
|
|
4
|
-
rowId:
|
|
5
|
-
colKey:
|
|
4
|
+
rowId: PropertyKey;
|
|
5
|
+
colKey: PropertyKey;
|
|
6
6
|
};
|
|
7
7
|
export type LazyOptions<Lazy extends boolean> = Lazy extends true ? {
|
|
8
8
|
lazy: MaybeRefOrGetter<{
|
|
@@ -29,7 +29,7 @@ export type CreateDataGridOptions<Lazy extends boolean> = {
|
|
|
29
29
|
*/
|
|
30
30
|
multiselectable?: boolean;
|
|
31
31
|
loading?: MaybeRefOrGetter<boolean>;
|
|
32
|
-
selectedCell?: Ref<CellIdentifier>;
|
|
32
|
+
selectedCell?: Ref<CellIdentifier | undefined>;
|
|
33
33
|
} & LazyOptions<Lazy>;
|
|
34
34
|
export type TrOptions<Lazy extends boolean> = {
|
|
35
35
|
rowId: PropertyKey;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './composables/calendar/createCalendar.js';
|
|
2
2
|
export * from './composables/comboBox/createComboBox.js';
|
|
3
|
+
export * from './composables/dataGrid/createDataGrid.js';
|
|
3
4
|
export * from './composables/helpers/useGlobalListener.js';
|
|
4
5
|
export * from './composables/helpers/useOutsideClick.js';
|
|
5
6
|
export * from './composables/listbox/createListbox.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { shallowRef, computed, toValue, ref, watch, nextTick, reactive, onBeforeMount, watchEffect, onBeforeUnmount, unref, useId, toRef } from "vue";
|
|
1
|
+
import { shallowRef, computed, toValue, ref, watch, nextTick, reactive, onBeforeMount, watchEffect, onBeforeUnmount, unref, useId, toRef, onMounted } from "vue";
|
|
2
2
|
const createBuilder = (builder) => builder;
|
|
3
3
|
function createElRef() {
|
|
4
4
|
const elementRef = shallowRef(null);
|
|
@@ -747,6 +747,247 @@ const createComboBox = createBuilder(
|
|
|
747
747
|
};
|
|
748
748
|
}
|
|
749
749
|
);
|
|
750
|
+
const useAllSettled = (cb) => {
|
|
751
|
+
const active = ref(false);
|
|
752
|
+
const allPromises = [];
|
|
753
|
+
let latestPromise = Promise.resolve();
|
|
754
|
+
const add = (promise) => {
|
|
755
|
+
active.value = true;
|
|
756
|
+
allPromises.push(promise);
|
|
757
|
+
const newAllSettled = Promise.allSettled(allPromises).then(() => {
|
|
758
|
+
if (newAllSettled === latestPromise) {
|
|
759
|
+
active.value = false;
|
|
760
|
+
allPromises.splice(0, allPromises.length);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
latestPromise = newAllSettled;
|
|
764
|
+
};
|
|
765
|
+
return { add, active };
|
|
766
|
+
};
|
|
767
|
+
const useLastSettled = (cb) => {
|
|
768
|
+
const active = ref(false);
|
|
769
|
+
let lastId = -1;
|
|
770
|
+
const add = (promise) => {
|
|
771
|
+
const promiseId = ++lastId;
|
|
772
|
+
active.value = true;
|
|
773
|
+
const onFinally = (success) => (resolved) => {
|
|
774
|
+
if (promiseId === lastId) {
|
|
775
|
+
active.value = false;
|
|
776
|
+
if (success) {
|
|
777
|
+
cb(success, resolved);
|
|
778
|
+
} else {
|
|
779
|
+
cb(success);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
promise.then(onFinally(true)).catch(onFinally(false));
|
|
784
|
+
};
|
|
785
|
+
const cancel = () => {
|
|
786
|
+
active.value = false;
|
|
787
|
+
lastId++;
|
|
788
|
+
};
|
|
789
|
+
return { active, add, cancel };
|
|
790
|
+
};
|
|
791
|
+
const COL_KEY_DATA_ATTR = "data-onyx-col-key";
|
|
792
|
+
const COL_INDEX_ARIA_ATTR = "aria-colindex";
|
|
793
|
+
const ROW_ID_DATA_ATTR = "data-onyx-row-id";
|
|
794
|
+
const ROW_INDEX_ARIA_ATTR = "aria-rowindex";
|
|
795
|
+
const StaticResolver = {
|
|
796
|
+
mapCellToIndex: (cell) => Array.from(cell.closest("tr")?.cells ?? []).indexOf(cell),
|
|
797
|
+
mapRowToIndex: (row) => Array.from(row.closest("table")?.rows ?? []).indexOf(row),
|
|
798
|
+
resolveCell: async (cellIndex, rowIndex, table) => table.rows.item(rowIndex)?.cells.item(cellIndex),
|
|
799
|
+
getTotalRows: (table) => table.rows.length,
|
|
800
|
+
getTotalCols: (table) => table.rows.item(0)?.cells.length ?? 0
|
|
801
|
+
};
|
|
802
|
+
const LazyResolverFactory = ({
|
|
803
|
+
rows,
|
|
804
|
+
cols,
|
|
805
|
+
requestLazyLoad
|
|
806
|
+
}) => ({
|
|
807
|
+
mapCellToIndex: (cell) => Number(cell.getAttribute(COL_INDEX_ARIA_ATTR)) - 1,
|
|
808
|
+
mapRowToIndex: (row) => Number(row.getAttribute(ROW_INDEX_ARIA_ATTR)) - 1,
|
|
809
|
+
resolveCell: async (cellIndex, rowIndex, table) => {
|
|
810
|
+
const queryCell = () => table.querySelector(
|
|
811
|
+
`*[${ROW_INDEX_ARIA_ATTR}="${rowIndex + 1}"] *[${COL_INDEX_ARIA_ATTR}="${cellIndex + 1}"]`
|
|
812
|
+
);
|
|
813
|
+
let cell = queryCell();
|
|
814
|
+
if (cell) {
|
|
815
|
+
return cell;
|
|
816
|
+
}
|
|
817
|
+
await requestLazyLoad(cellIndex, rowIndex);
|
|
818
|
+
cell = queryCell();
|
|
819
|
+
if (cell) {
|
|
820
|
+
return cell;
|
|
821
|
+
}
|
|
822
|
+
throw new Error(
|
|
823
|
+
`Table cell with row index "${rowIndex}" and column index "${cellIndex}" was not found after requested lazy loading and is unable to be focused!`
|
|
824
|
+
);
|
|
825
|
+
},
|
|
826
|
+
getTotalRows: () => toValue(rows),
|
|
827
|
+
getTotalCols: () => toValue(cols)
|
|
828
|
+
});
|
|
829
|
+
const createDataGrid = createBuilder(
|
|
830
|
+
(options) => {
|
|
831
|
+
const tableElement = createElRef();
|
|
832
|
+
const lazy = options.lazy && toRef(options.lazy);
|
|
833
|
+
const busy = computed(() => toValue(options.loading) ?? busySet.active.value);
|
|
834
|
+
const resolver = lazy ? LazyResolverFactory({
|
|
835
|
+
cols: () => lazy.value.totalCols,
|
|
836
|
+
rows: () => lazy.value.totalRows,
|
|
837
|
+
requestLazyLoad: lazy.value.requestLazyLoad
|
|
838
|
+
}) : StaticResolver;
|
|
839
|
+
const labelId = useId();
|
|
840
|
+
const selectedCell = options.selectedCell || ref();
|
|
841
|
+
const selectedCellEl = createElRef();
|
|
842
|
+
const focusQueue = useLastSettled((success, cell) => {
|
|
843
|
+
if (success) {
|
|
844
|
+
cell?.focus();
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
const busySet = useAllSettled();
|
|
848
|
+
const findFirstCell = () => tableElement.value?.querySelector?.(
|
|
849
|
+
`[${ROW_ID_DATA_ATTR}] [${COL_KEY_DATA_ATTR}]`
|
|
850
|
+
);
|
|
851
|
+
const setSelected = (element) => {
|
|
852
|
+
const colKey = element.closest(`[${COL_KEY_DATA_ATTR}]`)?.getAttribute(COL_KEY_DATA_ATTR);
|
|
853
|
+
const rowId = element.closest(`[${ROW_ID_DATA_ATTR}]`)?.getAttribute(ROW_ID_DATA_ATTR);
|
|
854
|
+
if (colKey && rowId) {
|
|
855
|
+
selectedCell.value = {
|
|
856
|
+
rowId,
|
|
857
|
+
colKey
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
const ensureTabTarget = () => {
|
|
862
|
+
if (selectedCell.value && selectedCellEl.value?.isConnected) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const firstCell = findFirstCell();
|
|
866
|
+
if (firstCell) {
|
|
867
|
+
setSelected(firstCell);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
let mutationObserver;
|
|
871
|
+
onMounted(() => {
|
|
872
|
+
ensureTabTarget();
|
|
873
|
+
mutationObserver = new MutationObserver(ensureTabTarget);
|
|
874
|
+
watch(
|
|
875
|
+
tableElement,
|
|
876
|
+
() => {
|
|
877
|
+
if (!tableElement.value) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
mutationObserver?.disconnect();
|
|
881
|
+
mutationObserver?.observe(tableElement.value, {
|
|
882
|
+
childList: true,
|
|
883
|
+
attributes: true,
|
|
884
|
+
subtree: true,
|
|
885
|
+
attributeFilter: ["value"]
|
|
886
|
+
});
|
|
887
|
+
},
|
|
888
|
+
{ immediate: true }
|
|
889
|
+
);
|
|
890
|
+
});
|
|
891
|
+
onBeforeUnmount(() => {
|
|
892
|
+
mutationObserver?.disconnect();
|
|
893
|
+
});
|
|
894
|
+
const onFocusin = (event) => {
|
|
895
|
+
setSelected(event.target);
|
|
896
|
+
focusQueue.cancel();
|
|
897
|
+
};
|
|
898
|
+
const onKeydown = (event) => {
|
|
899
|
+
const target = event.target;
|
|
900
|
+
const cellElement = target.closest("td, th");
|
|
901
|
+
const rowElement = target.closest("tr");
|
|
902
|
+
const tableElement2 = target.closest("table");
|
|
903
|
+
if (!cellElement || !rowElement || !tableElement2) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const { getTotalRows, getTotalCols, mapRowToIndex, mapCellToIndex, resolveCell } = resolver;
|
|
907
|
+
const colIndex = mapCellToIndex(cellElement);
|
|
908
|
+
const rowIndex = mapRowToIndex(rowElement);
|
|
909
|
+
const totalRows = getTotalRows(tableElement2);
|
|
910
|
+
const totalCols = getTotalCols(tableElement2);
|
|
911
|
+
let newColIndex = colIndex;
|
|
912
|
+
let newRowIndex = rowIndex;
|
|
913
|
+
if (wasKeyPressed(event, { ctrlKey: true, key: "Home" })) {
|
|
914
|
+
newColIndex = 0;
|
|
915
|
+
newRowIndex = 0;
|
|
916
|
+
} else if (wasKeyPressed(event, { ctrlKey: true, key: "End" })) {
|
|
917
|
+
newColIndex = totalCols === "unknown" ? Infinity : totalCols - 1;
|
|
918
|
+
newRowIndex = totalRows === "unknown" ? Infinity : totalRows - 1;
|
|
919
|
+
} else if (wasKeyPressed(event, "ArrowUp")) {
|
|
920
|
+
newRowIndex = rowIndex - 1;
|
|
921
|
+
} else if (wasKeyPressed(event, "ArrowDown")) {
|
|
922
|
+
newRowIndex = rowIndex + 1;
|
|
923
|
+
} else if (wasKeyPressed(event, "ArrowLeft")) {
|
|
924
|
+
newColIndex = colIndex - 1;
|
|
925
|
+
} else if (wasKeyPressed(event, "ArrowRight")) {
|
|
926
|
+
newColIndex = colIndex + 1;
|
|
927
|
+
} else if (wasKeyPressed(event, "Home")) {
|
|
928
|
+
newColIndex = 0;
|
|
929
|
+
} else if (wasKeyPressed(event, "End")) {
|
|
930
|
+
newColIndex = totalCols === "unknown" ? Infinity : totalCols - 1;
|
|
931
|
+
} else {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
event.preventDefault();
|
|
935
|
+
const maxRows = totalRows === "unknown" ? Infinity : totalRows - 1;
|
|
936
|
+
newRowIndex = Math.max(Math.min(newRowIndex, maxRows), 0);
|
|
937
|
+
const maxCols = totalCols === "unknown" ? Infinity : totalCols - 1;
|
|
938
|
+
newColIndex = Math.max(Math.min(newColIndex, maxCols), 0);
|
|
939
|
+
(async () => {
|
|
940
|
+
const promiseResolveCell = resolveCell(newColIndex, newRowIndex, tableElement2);
|
|
941
|
+
focusQueue.add(promiseResolveCell);
|
|
942
|
+
busySet.add(promiseResolveCell);
|
|
943
|
+
})();
|
|
944
|
+
};
|
|
945
|
+
return {
|
|
946
|
+
elements: {
|
|
947
|
+
label: {
|
|
948
|
+
id: labelId
|
|
949
|
+
},
|
|
950
|
+
table: computed(
|
|
951
|
+
() => ({
|
|
952
|
+
ref: tableElement,
|
|
953
|
+
onFocusin,
|
|
954
|
+
onKeydown,
|
|
955
|
+
role: "grid",
|
|
956
|
+
"aria-busy": busy.value,
|
|
957
|
+
"aria-labelledby": labelId,
|
|
958
|
+
"aria-rowcount": lazy?.value.totalRows === "unknown" ? -1 : lazy?.value.totalRows,
|
|
959
|
+
"aria-colcount": lazy?.value.totalCols === "unknown" ? -1 : lazy?.value.totalCols
|
|
960
|
+
})
|
|
961
|
+
),
|
|
962
|
+
tr: ({ rowId, rowIndex }) => ({
|
|
963
|
+
[ROW_ID_DATA_ATTR]: rowId.toString(),
|
|
964
|
+
"aria-rowindex": rowIndex == void 0 ? void 0 : rowIndex + 1,
|
|
965
|
+
role: "row"
|
|
966
|
+
}),
|
|
967
|
+
td: computed(() => ({ rowId, colKey, colIndex }) => {
|
|
968
|
+
const isSelected = colKey.toString() === selectedCell.value?.colKey.toString() && rowId.toString() === selectedCell.value?.rowId.toString();
|
|
969
|
+
return {
|
|
970
|
+
tabindex: isSelected ? "0" : "-1",
|
|
971
|
+
ref: isSelected ? selectedCellEl : void 0,
|
|
972
|
+
[COL_KEY_DATA_ATTR]: colKey.toString(),
|
|
973
|
+
// TODO: handle symbols
|
|
974
|
+
"aria-colindex": colIndex == void 0 ? void 0 : colIndex + 1,
|
|
975
|
+
role: "cell"
|
|
976
|
+
};
|
|
977
|
+
})
|
|
978
|
+
},
|
|
979
|
+
state: {
|
|
980
|
+
/**
|
|
981
|
+
* Indicates that the data grid expects a content change soon, e.g. because more or other data is loaded.
|
|
982
|
+
* If `loading` is passed in via the options, this will mirror its value.
|
|
983
|
+
* Otherwise it will be dynamically set based on the running state of the `requestLazyLoad` promises.
|
|
984
|
+
*/
|
|
985
|
+
busy
|
|
986
|
+
},
|
|
987
|
+
internals: {}
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
);
|
|
750
991
|
const createMenuButton = createBuilder((options) => {
|
|
751
992
|
const rootId = useId();
|
|
752
993
|
const menuId = useId();
|
|
@@ -1426,6 +1667,7 @@ export {
|
|
|
1426
1667
|
_unstableCreateCalendar,
|
|
1427
1668
|
createBuilder,
|
|
1428
1669
|
createComboBox,
|
|
1670
|
+
createDataGrid,
|
|
1429
1671
|
createElRef,
|
|
1430
1672
|
createListbox,
|
|
1431
1673
|
createMenuButton,
|