@spfx-fast/sharepoint-ui 0.0.0-stage → 0.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.
- package/AGENTS.md +211 -0
- package/LICENSE +21 -0
- package/README.md +897 -2
- package/components.json +5810 -0
- package/dist/appearance.d.ts +6 -0
- package/dist/appearance.js +217 -0
- package/dist/elements/accordion.d.ts +9 -0
- package/dist/elements/accordion.js +338 -0
- package/dist/elements/alert.d.ts +2 -0
- package/dist/elements/alert.js +62 -0
- package/dist/elements/avatar-group-primitives.d.ts +3 -0
- package/dist/elements/avatar-group-primitives.js +189 -0
- package/dist/elements/avatar-group.d.ts +2 -0
- package/dist/elements/avatar-group.js +110 -0
- package/dist/elements/avatar.d.ts +4 -0
- package/dist/elements/avatar.js +251 -0
- package/dist/elements/badge.d.ts +8 -0
- package/dist/elements/badge.js +182 -0
- package/dist/elements/breadcrumb.d.ts +4 -0
- package/dist/elements/breadcrumb.js +233 -0
- package/dist/elements/button-group.d.ts +1 -0
- package/dist/elements/button-group.js +20 -0
- package/dist/elements/button-variants.d.ts +6 -0
- package/dist/elements/button-variants.js +339 -0
- package/dist/elements/button.d.ts +5 -0
- package/dist/elements/button.js +233 -0
- package/dist/elements/card-primitives.d.ts +3 -0
- package/dist/elements/card-primitives.js +83 -0
- package/dist/elements/card.d.ts +5 -0
- package/dist/elements/card.js +213 -0
- package/dist/elements/carousel.d.ts +92 -0
- package/dist/elements/carousel.js +1112 -0
- package/dist/elements/color-picker.d.ts +24 -0
- package/dist/elements/color-picker.js +1004 -0
- package/dist/elements/combobox.d.ts +1 -0
- package/dist/elements/combobox.js +1012 -0
- package/dist/elements/data-grid-model.d.ts +117 -0
- package/dist/elements/data-grid-model.js +294 -0
- package/dist/elements/data-grid.d.ts +29 -0
- package/dist/elements/data-grid.js +1567 -0
- package/dist/elements/date-picker.d.ts +1 -0
- package/dist/elements/date-picker.js +486 -0
- package/dist/elements/dialog.d.ts +23 -0
- package/dist/elements/dialog.js +409 -0
- package/dist/elements/divider.d.ts +2 -0
- package/dist/elements/divider.js +66 -0
- package/dist/elements/drawer.d.ts +3 -0
- package/dist/elements/drawer.js +308 -0
- package/dist/elements/field-composition.d.ts +26 -0
- package/dist/elements/field-composition.js +133 -0
- package/dist/elements/field.d.ts +1 -0
- package/dist/elements/field.js +67 -0
- package/dist/elements/file-upload.d.ts +6 -0
- package/dist/elements/file-upload.js +211 -0
- package/dist/elements/floating-position.d.ts +26 -0
- package/dist/elements/floating-position.js +39 -0
- package/dist/elements/image.d.ts +28 -0
- package/dist/elements/image.js +38 -0
- package/dist/elements/info.d.ts +9 -0
- package/dist/elements/info.js +102 -0
- package/dist/elements/input.d.ts +5 -0
- package/dist/elements/input.js +255 -0
- package/dist/elements/label.d.ts +15 -0
- package/dist/elements/label.js +56 -0
- package/dist/elements/link.d.ts +1 -0
- package/dist/elements/link.js +92 -0
- package/dist/elements/list.d.ts +19 -0
- package/dist/elements/list.js +297 -0
- package/dist/elements/menu.d.ts +33 -0
- package/dist/elements/menu.js +1103 -0
- package/dist/elements/message-bar.d.ts +10 -0
- package/dist/elements/message-bar.js +299 -0
- package/dist/elements/nav.d.ts +32 -0
- package/dist/elements/nav.js +791 -0
- package/dist/elements/overflow.d.ts +9 -0
- package/dist/elements/overflow.js +218 -0
- package/dist/elements/persona.d.ts +3 -0
- package/dist/elements/persona.js +124 -0
- package/dist/elements/popover.d.ts +77 -0
- package/dist/elements/popover.js +840 -0
- package/dist/elements/progress-bar.d.ts +4 -0
- package/dist/elements/progress-bar.js +82 -0
- package/dist/elements/radio-group.d.ts +1 -0
- package/dist/elements/radio-group.js +303 -0
- package/dist/elements/rating-display.d.ts +2 -0
- package/dist/elements/rating-display.js +125 -0
- package/dist/elements/rating.d.ts +2 -0
- package/dist/elements/rating.js +206 -0
- package/dist/elements/rich-text-editor.d.ts +18 -0
- package/dist/elements/rich-text-editor.js +508 -0
- package/dist/elements/search.d.ts +3 -0
- package/dist/elements/search.js +207 -0
- package/dist/elements/select.d.ts +6 -0
- package/dist/elements/select.js +291 -0
- package/dist/elements/shared.d.ts +19 -0
- package/dist/elements/shared.js +396 -0
- package/dist/elements/skeleton.d.ts +6 -0
- package/dist/elements/skeleton.js +91 -0
- package/dist/elements/slider.d.ts +1 -0
- package/dist/elements/slider.js +248 -0
- package/dist/elements/spin-button.d.ts +7 -0
- package/dist/elements/spin-button.js +499 -0
- package/dist/elements/spinner.d.ts +4 -0
- package/dist/elements/spinner.js +124 -0
- package/dist/elements/swatch-picker.d.ts +5 -0
- package/dist/elements/swatch-picker.js +363 -0
- package/dist/elements/table.d.ts +21 -0
- package/dist/elements/table.js +261 -0
- package/dist/elements/tabs.d.ts +1 -0
- package/dist/elements/tabs.js +276 -0
- package/dist/elements/tag-picker.d.ts +29 -0
- package/dist/elements/tag-picker.js +697 -0
- package/dist/elements/tags.d.ts +21 -0
- package/dist/elements/tags.js +421 -0
- package/dist/elements/teaching-popover.d.ts +32 -0
- package/dist/elements/teaching-popover.js +412 -0
- package/dist/elements/text.d.ts +5 -0
- package/dist/elements/text.js +69 -0
- package/dist/elements/textarea.d.ts +1 -0
- package/dist/elements/textarea.js +182 -0
- package/dist/elements/toast.d.ts +25 -0
- package/dist/elements/toast.js +337 -0
- package/dist/elements/toaster.d.ts +6 -0
- package/dist/elements/toaster.js +356 -0
- package/dist/elements/toggle.d.ts +2 -0
- package/dist/elements/toggle.js +246 -0
- package/dist/elements/toolbar.d.ts +12 -0
- package/dist/elements/toolbar.js +381 -0
- package/dist/elements/tooltip.d.ts +76 -0
- package/dist/elements/tooltip.js +510 -0
- package/dist/elements/tree-model.d.ts +19 -0
- package/dist/elements/tree-model.js +34 -0
- package/dist/elements/tree.d.ts +5 -0
- package/dist/elements/tree.js +273 -0
- package/dist/elements/view-card.d.ts +2 -0
- package/dist/elements/view-card.js +78 -0
- package/dist/elements/virtual-list.d.ts +41 -0
- package/dist/elements/virtual-list.js +369 -0
- package/dist/elements.d.ts +72 -0
- package/dist/elements.js +207 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +66 -0
- package/dist/react-theme-styles.d.ts +2 -0
- package/dist/react-theme-styles.js +75 -0
- package/dist/react.d.ts +4540 -0
- package/dist/react.js +6799 -0
- package/dist/theme.d.ts +169 -0
- package/dist/theme.js +167 -0
- package/dist/virtualizer-hooks.d.ts +80 -0
- package/dist/virtualizer-hooks.js +253 -0
- package/package.json +87 -4
- package/scripts/generate-token-index.mjs +43 -0
- package/scripts/sync-component-catalog-signatures.mjs +55 -0
- package/scripts/validate-component-catalog.mjs +44 -0
- package/skills/sharepoint-ui/SKILL.md +12 -0
- package/skills/sharepoint-ui/references/choosing-components.md +20 -0
- package/skills/sharepoint-ui/references/sharepoint-patterns.md +17 -0
- package/tokens.json +3777 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export type DataGridSortDirection = "ascending" | "descending";
|
|
2
|
+
/** Keyboard entry and movement strategy for the DataGrid. */
|
|
3
|
+
export type DataGridFocusMode = "none" | "cell" | "row_unstable" | "composite";
|
|
4
|
+
export type DataGridSelectionAppearance = "brand" | "neutral" | "none";
|
|
5
|
+
export type DataGridSort = {
|
|
6
|
+
columnId: string;
|
|
7
|
+
direction: DataGridSortDirection;
|
|
8
|
+
};
|
|
9
|
+
export type DataGridSortState = DataGridSort | readonly DataGridSort[];
|
|
10
|
+
export type DataGridColumn<T = unknown> = {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
sortable?: boolean;
|
|
14
|
+
searchable?: boolean;
|
|
15
|
+
width?: string;
|
|
16
|
+
minWidth?: number;
|
|
17
|
+
maxWidth?: number;
|
|
18
|
+
resizable?: boolean;
|
|
19
|
+
editable?: boolean;
|
|
20
|
+
editType?: "text" | "number" | "date";
|
|
21
|
+
value?: (item: T) => string | number | Date | null | undefined;
|
|
22
|
+
};
|
|
23
|
+
export type DataGridColumnWidths = Readonly<Record<string, number>>;
|
|
24
|
+
export type DataGridDataSourceRow<T = unknown> = {
|
|
25
|
+
kind: "item";
|
|
26
|
+
item: T;
|
|
27
|
+
rowId?: string | number;
|
|
28
|
+
itemIndex?: number;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "group";
|
|
31
|
+
key: string;
|
|
32
|
+
label: string;
|
|
33
|
+
count: number;
|
|
34
|
+
expanded: boolean;
|
|
35
|
+
};
|
|
36
|
+
/** Compact selection for indexed queries; all mode selects the source's current matching rows except excluded keys. */
|
|
37
|
+
export type DataGridSelectionModel = {
|
|
38
|
+
mode: "explicit";
|
|
39
|
+
keys: readonly string[];
|
|
40
|
+
} | {
|
|
41
|
+
mode: "all";
|
|
42
|
+
excludedKeys: readonly string[];
|
|
43
|
+
scopeKey?: string;
|
|
44
|
+
};
|
|
45
|
+
export type DataGridClipboardColumn = Pick<DataGridColumn, "id" | "label">;
|
|
46
|
+
export declare function isDataGridRowSelected(selection: DataGridSelectionModel, key: string, scopeKey?: string): boolean;
|
|
47
|
+
export declare function setDataGridRowSelected(selection: DataGridSelectionModel, key: string, selected: boolean, scopeKey?: string): DataGridSelectionModel;
|
|
48
|
+
/** Indexed provider for server-owned grid order, filtering, grouping, and sorting. Length counts displayed rows, including group headers. */
|
|
49
|
+
export type DataGridDataSource<T = unknown> = {
|
|
50
|
+
readonly length: number;
|
|
51
|
+
/** Optional exact number of selectable item rows, excluding group headers. */
|
|
52
|
+
readonly selectableCount?: number;
|
|
53
|
+
/** Stable identity of the current server query; binds all-matching selection to that query. */
|
|
54
|
+
readonly selectionScopeKey?: string;
|
|
55
|
+
loadRange(start: number, end: number, signal: AbortSignal): void | Promise<void>;
|
|
56
|
+
/** Return TSV for the provider query and compact selection; implement for remote or all-matching copy. */
|
|
57
|
+
copySelection?(selection: DataGridSelectionModel, columns: readonly DataGridClipboardColumn[], signal: AbortSignal): string | Promise<string>;
|
|
58
|
+
subscribe?(listener: () => void): () => void;
|
|
59
|
+
} & ({
|
|
60
|
+
getItem(index: number): T | undefined;
|
|
61
|
+
getRow?: (index: number) => DataGridDataSourceRow<T> | undefined;
|
|
62
|
+
} | {
|
|
63
|
+
getRow(index: number): DataGridDataSourceRow<T> | undefined;
|
|
64
|
+
getItem?: (index: number) => T | undefined;
|
|
65
|
+
});
|
|
66
|
+
/** Start an async clipboard write during the user gesture; ClipboardItem accepts a promised Blob. */
|
|
67
|
+
export declare function writeDataGridClipboardText(text: string | Promise<string>): Promise<void>;
|
|
68
|
+
export declare function getDataGridDataSourceRow<T>(source: DataGridDataSource<T>, index: number): DataGridDataSourceRow<T> | undefined;
|
|
69
|
+
/** Per-column sizing configuration compatible with Fluent DataGrid sizing options. */
|
|
70
|
+
export type DataGridColumnSizingOption = {
|
|
71
|
+
defaultWidth?: number;
|
|
72
|
+
idealWidth?: number;
|
|
73
|
+
minWidth?: number;
|
|
74
|
+
padding?: number;
|
|
75
|
+
autoFitColumns?: boolean;
|
|
76
|
+
};
|
|
77
|
+
export type DataGridColumnSizingOptions = Readonly<Record<string, DataGridColumnSizingOption>>;
|
|
78
|
+
export declare function getDataGridColumnSizing<T>(column: DataGridColumn<T>, options?: DataGridColumnSizingOptions): Required<Pick<DataGridColumnSizingOption, "defaultWidth" | "idealWidth" | "minWidth" | "padding" | "autoFitColumns">>;
|
|
79
|
+
/** Apply Fluent's grow-to-ideal / shrink-from-end auto-fit policy to configured tracks. */
|
|
80
|
+
export declare function fitDataGridColumnWidths<T>(columns: readonly DataGridColumn<T>[], viewportWidth: number, options?: DataGridColumnSizingOptions, currentWidths?: DataGridColumnWidths, reservedWidth?: number): DataGridColumnWidths;
|
|
81
|
+
export type DataGridGroup<T = unknown> = {
|
|
82
|
+
key: string;
|
|
83
|
+
label: string;
|
|
84
|
+
value: string | number | Date | null | undefined;
|
|
85
|
+
items: T[];
|
|
86
|
+
indices: number[];
|
|
87
|
+
};
|
|
88
|
+
export type DataGridEditValue = string | number | null;
|
|
89
|
+
export type DataGridCellEdit<T = unknown> = {
|
|
90
|
+
item: T;
|
|
91
|
+
rowId: string;
|
|
92
|
+
columnId: string;
|
|
93
|
+
previousValue: unknown;
|
|
94
|
+
value: DataGridEditValue;
|
|
95
|
+
};
|
|
96
|
+
/** Bound row sizes to keep a consumer callback from creating pathological virtual scroll extents. */
|
|
97
|
+
export declare function clampDataGridRowHeight(height: number | undefined, estimate: number): number;
|
|
98
|
+
export declare function getDataGridCellValue<T>(item: T, column: DataGridColumn<T>): unknown;
|
|
99
|
+
export declare function toDataGridInputValue(value: unknown, type?: DataGridColumn["editType"]): string;
|
|
100
|
+
export declare function parseDataGridInputValue(value: string, type?: DataGridColumn["editType"]): DataGridEditValue;
|
|
101
|
+
/** Parse tab-separated clipboard text, including quoted tabs, newlines, and doubled quotes. */
|
|
102
|
+
export declare function parseDataGridClipboard(text: string): string[][];
|
|
103
|
+
export declare function formatDataGridClipboard(rows: readonly (readonly unknown[])[]): string;
|
|
104
|
+
export declare function parseDataGridClipboardValue(value: string, type?: DataGridColumn["editType"]): DataGridEditValue;
|
|
105
|
+
/** Locale-aware contains filter across searchable columns, preserving source order. */
|
|
106
|
+
export declare function filterDataGridItems<T>(items: readonly T[], columns: readonly DataGridColumn<T>[], query: string, locale?: string): T[];
|
|
107
|
+
export declare function getDataGridGroupKey(columnId: string, value: string | number | Date | null | undefined): string;
|
|
108
|
+
export declare function groupDataGridItems<T>(items: readonly T[], columns: readonly DataGridColumn<T>[], groupBy: string | undefined, locale?: string, emptyLabel?: string): DataGridGroup<T>[] | undefined;
|
|
109
|
+
export declare function dataGridColumnVar(columnId: string): string;
|
|
110
|
+
/** Find the visible column interval from laid-out header cells, including RTL order. */
|
|
111
|
+
export declare function getDataGridColumnWindow(cells: readonly HTMLElement[], viewport: HTMLElement, overscan?: number): {
|
|
112
|
+
start: number;
|
|
113
|
+
end: number;
|
|
114
|
+
};
|
|
115
|
+
export declare function clampDataGridColumnWidth<T>(column: DataGridColumn<T>, width: number, sizing?: DataGridColumnSizingOption): number;
|
|
116
|
+
/** Stable, locale-aware sort shared by the React adapter and custom element. */
|
|
117
|
+
export declare function sortDataGridItems<T>(items: readonly T[], columns: readonly DataGridColumn<T>[], sort?: DataGridSortState, locale?: string): T[];
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
export function isDataGridRowSelected(selection, key, scopeKey) {
|
|
2
|
+
return selection.mode === "all" ? (selection.scopeKey === undefined || selection.scopeKey === scopeKey) && !selection.excludedKeys.includes(key) : selection.keys.includes(key);
|
|
3
|
+
}
|
|
4
|
+
export function setDataGridRowSelected(selection, key, selected, scopeKey) {
|
|
5
|
+
if (selection.mode === "all" && (selection.scopeKey === undefined || selection.scopeKey === scopeKey)) {
|
|
6
|
+
const excludedKeys = new Set(selection.excludedKeys);
|
|
7
|
+
if (selected)
|
|
8
|
+
excludedKeys.delete(key);
|
|
9
|
+
else
|
|
10
|
+
excludedKeys.add(key);
|
|
11
|
+
return { mode: "all", excludedKeys: [...excludedKeys], ...(selection.scopeKey === undefined ? {} : { scopeKey: selection.scopeKey }) };
|
|
12
|
+
}
|
|
13
|
+
const keys = new Set(selection.mode === "explicit" ? selection.keys : []);
|
|
14
|
+
if (selected)
|
|
15
|
+
keys.add(key);
|
|
16
|
+
else
|
|
17
|
+
keys.delete(key);
|
|
18
|
+
return { mode: "explicit", keys: [...keys] };
|
|
19
|
+
}
|
|
20
|
+
/** Start an async clipboard write during the user gesture; ClipboardItem accepts a promised Blob. */
|
|
21
|
+
export function writeDataGridClipboardText(text) {
|
|
22
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
23
|
+
if (!clipboard)
|
|
24
|
+
return Promise.reject(new Error("Clipboard access is unavailable in this browser."));
|
|
25
|
+
if (clipboard.write && typeof globalThis.ClipboardItem === "function") {
|
|
26
|
+
const value = Promise.resolve(text).then(result => new Blob([result], { type: "text/plain" }));
|
|
27
|
+
return clipboard.write([new ClipboardItem({ "text/plain": value })]);
|
|
28
|
+
}
|
|
29
|
+
return Promise.resolve(text).then(result => {
|
|
30
|
+
if (!clipboard.writeText)
|
|
31
|
+
throw new Error("Async clipboard writing is not supported in this browser.");
|
|
32
|
+
return clipboard.writeText(result);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function getDataGridDataSourceRow(source, index) {
|
|
36
|
+
const row = source.getRow?.(index);
|
|
37
|
+
if (row)
|
|
38
|
+
return row;
|
|
39
|
+
const item = source.getItem?.(index);
|
|
40
|
+
return item === undefined ? undefined : { kind: "item", item };
|
|
41
|
+
}
|
|
42
|
+
export function getDataGridColumnSizing(column, options) {
|
|
43
|
+
const configured = options?.[column.id];
|
|
44
|
+
const minWidth = Number.isFinite(configured?.minWidth) && configured.minWidth > 0 ? configured.minWidth : Number.isFinite(column.minWidth) && column.minWidth > 0 ? column.minWidth : configured ? 100 : 48;
|
|
45
|
+
const maxWidth = Number.isFinite(column.maxWidth) && column.maxWidth >= minWidth ? column.maxWidth : 1600;
|
|
46
|
+
const cssWidth = typeof column.width === "string" && /^\d+(?:\.\d+)?px$/.test(column.width) ? Number.parseFloat(column.width) : undefined;
|
|
47
|
+
const idealWidth = Math.round(Math.max(minWidth, Math.min(maxWidth, Number.isFinite(configured?.idealWidth) && configured.idealWidth > 0 ? configured.idealWidth : Number.isFinite(configured?.defaultWidth) && configured.defaultWidth > 0 ? configured.defaultWidth : 150)));
|
|
48
|
+
const defaultWidth = Math.round(Math.max(minWidth, Math.min(maxWidth, Number.isFinite(configured?.defaultWidth) && configured.defaultWidth > 0 ? configured.defaultWidth : cssWidth ?? idealWidth)));
|
|
49
|
+
return { defaultWidth, idealWidth, minWidth, padding: Number.isFinite(configured?.padding) && configured.padding >= 0 ? configured.padding : 16, autoFitColumns: configured?.autoFitColumns !== false };
|
|
50
|
+
}
|
|
51
|
+
/** Apply Fluent's grow-to-ideal / shrink-from-end auto-fit policy to configured tracks. */
|
|
52
|
+
export function fitDataGridColumnWidths(columns, viewportWidth, options, currentWidths = {}, reservedWidth = 0) {
|
|
53
|
+
if (!Number.isFinite(viewportWidth) || viewportWidth <= 0 || !columns.length)
|
|
54
|
+
return {};
|
|
55
|
+
const state = columns.map(column => {
|
|
56
|
+
const sizing = getDataGridColumnSizing(column, options);
|
|
57
|
+
const minWidth = sizing.minWidth;
|
|
58
|
+
const maxWidth = Number.isFinite(column.maxWidth) && column.maxWidth >= minWidth ? column.maxWidth : 1600;
|
|
59
|
+
const current = currentWidths[column.id];
|
|
60
|
+
const width = Math.max(minWidth, Math.min(maxWidth, Number.isFinite(current) && current > 0 ? current : sizing.defaultWidth));
|
|
61
|
+
const idealWidth = Number.isFinite(current) && current > 0 ? width : Math.max(width, Math.min(maxWidth, sizing.idealWidth));
|
|
62
|
+
return { column, width, idealWidth, minWidth, maxWidth, padding: sizing.padding };
|
|
63
|
+
});
|
|
64
|
+
const available = Math.max(0, viewportWidth - Math.max(0, reservedWidth) + state.reduce((sum, entry) => sum + entry.padding, 0));
|
|
65
|
+
let total = state.reduce((sum, entry) => sum + entry.width + entry.padding, 0);
|
|
66
|
+
if (total < available) {
|
|
67
|
+
let remaining = available - total;
|
|
68
|
+
for (const entry of state) {
|
|
69
|
+
const grow = Math.min(Math.max(0, entry.idealWidth - entry.width), remaining);
|
|
70
|
+
entry.width += grow;
|
|
71
|
+
remaining -= grow;
|
|
72
|
+
if (remaining <= 0)
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (remaining > 0) {
|
|
76
|
+
const last = state[state.length - 1];
|
|
77
|
+
const grow = Math.min(remaining, Math.max(0, last.maxWidth - last.width));
|
|
78
|
+
last.width += grow;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (total > available) {
|
|
82
|
+
let remaining = total - available;
|
|
83
|
+
for (let index = state.length - 1; index >= 0 && remaining > 0; index--) {
|
|
84
|
+
const entry = state[index];
|
|
85
|
+
const shrink = Math.min(Math.max(0, entry.width - entry.minWidth), remaining);
|
|
86
|
+
entry.width -= shrink;
|
|
87
|
+
remaining -= shrink;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return Object.fromEntries(state.map(entry => [entry.column.id, Math.round(entry.width)]));
|
|
91
|
+
}
|
|
92
|
+
/** Bound row sizes to keep a consumer callback from creating pathological virtual scroll extents. */
|
|
93
|
+
export function clampDataGridRowHeight(height, estimate) {
|
|
94
|
+
return Number.isFinite(height) && height > 0 ? Math.max(1, Math.min(10_000, height)) : estimate;
|
|
95
|
+
}
|
|
96
|
+
export function getDataGridCellValue(item, column) {
|
|
97
|
+
return column.value ? column.value(item) : item?.[column.id];
|
|
98
|
+
}
|
|
99
|
+
export function toDataGridInputValue(value, type = "text") {
|
|
100
|
+
if (value == null)
|
|
101
|
+
return "";
|
|
102
|
+
if (type === "date" && value instanceof Date)
|
|
103
|
+
return Number.isFinite(value.valueOf()) ? value.toISOString().slice(0, 10) : "";
|
|
104
|
+
return String(value);
|
|
105
|
+
}
|
|
106
|
+
export function parseDataGridInputValue(value, type = "text") {
|
|
107
|
+
if (value === "")
|
|
108
|
+
return null;
|
|
109
|
+
if (type === "number") {
|
|
110
|
+
const number = Number(value);
|
|
111
|
+
return Number.isFinite(number) ? number : value;
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
/** Parse tab-separated clipboard text, including quoted tabs, newlines, and doubled quotes. */
|
|
116
|
+
export function parseDataGridClipboard(text) {
|
|
117
|
+
if (text.length > 1_000_000)
|
|
118
|
+
throw new Error("Clipboard data is limited to 1,000,000 characters.");
|
|
119
|
+
const rows = [];
|
|
120
|
+
let row = [], cell = "", quoted = false;
|
|
121
|
+
for (let index = 0; index < text.length; index++) {
|
|
122
|
+
const character = text[index];
|
|
123
|
+
if (quoted) {
|
|
124
|
+
if (character === '"' && text[index + 1] === '"') {
|
|
125
|
+
cell += '"';
|
|
126
|
+
index++;
|
|
127
|
+
}
|
|
128
|
+
else if (character === '"')
|
|
129
|
+
quoted = false;
|
|
130
|
+
else
|
|
131
|
+
cell += character;
|
|
132
|
+
}
|
|
133
|
+
else if (character === '"' && cell.length === 0)
|
|
134
|
+
quoted = true;
|
|
135
|
+
else if (character === "\t") {
|
|
136
|
+
row.push(cell);
|
|
137
|
+
cell = "";
|
|
138
|
+
}
|
|
139
|
+
else if (character === "\r" || character === "\n") {
|
|
140
|
+
row.push(cell);
|
|
141
|
+
rows.push(row);
|
|
142
|
+
row = [];
|
|
143
|
+
cell = "";
|
|
144
|
+
if (character === "\r" && text[index + 1] === "\n")
|
|
145
|
+
index++;
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
cell += character;
|
|
149
|
+
}
|
|
150
|
+
if (quoted)
|
|
151
|
+
throw new Error("Clipboard data contains an unfinished quoted cell.");
|
|
152
|
+
if (cell.length || row.length || !rows.length) {
|
|
153
|
+
row.push(cell);
|
|
154
|
+
rows.push(row);
|
|
155
|
+
}
|
|
156
|
+
return rows;
|
|
157
|
+
}
|
|
158
|
+
export function formatDataGridClipboard(rows) {
|
|
159
|
+
return rows.map(row => row.map(raw => {
|
|
160
|
+
const value = raw instanceof Date ? Number.isFinite(raw.valueOf()) ? raw.toISOString() : "" : raw == null ? "" : String(raw);
|
|
161
|
+
return /[\t\r\n"]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
162
|
+
}).join("\t")).join("\r\n");
|
|
163
|
+
}
|
|
164
|
+
export function parseDataGridClipboardValue(value, type = "text") {
|
|
165
|
+
if (type === "number") {
|
|
166
|
+
const normalized = value.trim();
|
|
167
|
+
if (!normalized)
|
|
168
|
+
return null;
|
|
169
|
+
const number = Number(normalized);
|
|
170
|
+
if (!Number.isFinite(number))
|
|
171
|
+
throw new Error(`“${value}” is not a valid number.`);
|
|
172
|
+
return number;
|
|
173
|
+
}
|
|
174
|
+
if (type === "date") {
|
|
175
|
+
const normalized = value.trim();
|
|
176
|
+
if (!normalized)
|
|
177
|
+
return null;
|
|
178
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized) || new Date(`${normalized}T00:00:00Z`).toISOString().slice(0, 10) !== normalized)
|
|
179
|
+
throw new Error(`“${value}” is not a valid date.`);
|
|
180
|
+
return normalized;
|
|
181
|
+
}
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
/** Locale-aware contains filter across searchable columns, preserving source order. */
|
|
185
|
+
export function filterDataGridItems(items, columns, query, locale) {
|
|
186
|
+
const normalized = query.trim();
|
|
187
|
+
if (!normalized)
|
|
188
|
+
return [...items];
|
|
189
|
+
let searchLocale;
|
|
190
|
+
try {
|
|
191
|
+
searchLocale = locale?.trim() || undefined;
|
|
192
|
+
normalized.toLocaleLowerCase(searchLocale);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
searchLocale = undefined;
|
|
196
|
+
}
|
|
197
|
+
const needle = normalized.toLocaleLowerCase(searchLocale);
|
|
198
|
+
const searchableColumns = columns.filter(column => column.searchable !== false);
|
|
199
|
+
if (!searchableColumns.length)
|
|
200
|
+
return [...items];
|
|
201
|
+
return items.filter(item => searchableColumns.some(column => {
|
|
202
|
+
const value = column.value ? column.value(item) : item?.[column.id];
|
|
203
|
+
if (value == null)
|
|
204
|
+
return false;
|
|
205
|
+
const text = value instanceof Date ? Number.isFinite(value.valueOf()) ? `${value.toISOString()} ${value.toLocaleDateString(searchLocale)}` : "" : Array.isArray(value) ? value.join(" ") : typeof value === "object" ? "" : String(value);
|
|
206
|
+
return text.toLocaleLowerCase(searchLocale).includes(needle);
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
export function getDataGridGroupKey(columnId, value) {
|
|
210
|
+
const type = value instanceof Date ? "date" : value === null || value === undefined ? "empty" : typeof value;
|
|
211
|
+
const normalized = value instanceof Date ? Number.isFinite(value.valueOf()) ? value.toISOString() : "invalid" : value === null || value === undefined ? "" : String(value);
|
|
212
|
+
return JSON.stringify([columnId, type, normalized]);
|
|
213
|
+
}
|
|
214
|
+
export function groupDataGridItems(items, columns, groupBy, locale, emptyLabel = "No value") {
|
|
215
|
+
const column = groupBy ? columns.find(candidate => candidate.id === groupBy) : undefined;
|
|
216
|
+
if (!column)
|
|
217
|
+
return undefined;
|
|
218
|
+
const groups = new Map();
|
|
219
|
+
items.forEach((item, index) => {
|
|
220
|
+
const value = column.value ? column.value(item) : item?.[column.id];
|
|
221
|
+
const key = getDataGridGroupKey(column.id, value);
|
|
222
|
+
let group = groups.get(key);
|
|
223
|
+
if (!group) {
|
|
224
|
+
const label = value === null || value === undefined ? emptyLabel : value instanceof Date ? Number.isFinite(value.valueOf()) ? value.toLocaleDateString(locale || undefined) : emptyLabel : String(value);
|
|
225
|
+
group = { key, label, value, items: [], indices: [] };
|
|
226
|
+
groups.set(key, group);
|
|
227
|
+
}
|
|
228
|
+
group.items.push(item);
|
|
229
|
+
group.indices.push(index);
|
|
230
|
+
});
|
|
231
|
+
return [...groups.values()];
|
|
232
|
+
}
|
|
233
|
+
export function dataGridColumnVar(columnId) {
|
|
234
|
+
const suffix = Array.from(columnId, char => `_${char.codePointAt(0).toString(16)}_`).join("");
|
|
235
|
+
return `--spui-data-grid-column-${suffix}`;
|
|
236
|
+
}
|
|
237
|
+
/** Find the visible column interval from laid-out header cells, including RTL order. */
|
|
238
|
+
export function getDataGridColumnWindow(cells, viewport, overscan = 2) {
|
|
239
|
+
if (!cells.length)
|
|
240
|
+
return { start: 0, end: 0 };
|
|
241
|
+
const bounds = viewport.getBoundingClientRect();
|
|
242
|
+
const left = bounds.left + viewport.clientLeft;
|
|
243
|
+
const right = left + viewport.clientWidth;
|
|
244
|
+
const rtl = getComputedStyle(viewport).direction === "rtl";
|
|
245
|
+
const firstVisible = (cell) => rtl ? cell.getBoundingClientRect().left < right : cell.getBoundingClientRect().right > left;
|
|
246
|
+
const lastVisible = (cell) => rtl ? cell.getBoundingClientRect().right > left : cell.getBoundingClientRect().left < right;
|
|
247
|
+
const lowerBound = (predicate) => {
|
|
248
|
+
let low = 0, high = cells.length;
|
|
249
|
+
while (low < high) {
|
|
250
|
+
const middle = (low + high) >>> 1;
|
|
251
|
+
if (predicate(cells[middle]))
|
|
252
|
+
high = middle;
|
|
253
|
+
else
|
|
254
|
+
low = middle + 1;
|
|
255
|
+
}
|
|
256
|
+
return low;
|
|
257
|
+
};
|
|
258
|
+
let start = lowerBound(firstVisible);
|
|
259
|
+
let afterLast = lowerBound(cell => !lastVisible(cell));
|
|
260
|
+
if (start >= cells.length || afterLast <= start)
|
|
261
|
+
return { start: 0, end: cells.length };
|
|
262
|
+
const buffer = Number.isFinite(overscan) ? Math.max(0, Math.min(20, Math.floor(overscan))) : 2;
|
|
263
|
+
start = Math.max(0, start - buffer);
|
|
264
|
+
return { start, end: Math.min(cells.length, afterLast + buffer) };
|
|
265
|
+
}
|
|
266
|
+
export function clampDataGridColumnWidth(column, width, sizing) {
|
|
267
|
+
const min = Number.isFinite(sizing?.minWidth) && sizing.minWidth > 0 ? sizing.minWidth : Number.isFinite(column.minWidth) && column.minWidth > 0 ? column.minWidth : 48;
|
|
268
|
+
const max = Number.isFinite(column.maxWidth) && column.maxWidth >= min ? column.maxWidth : 1600;
|
|
269
|
+
return Math.round(Math.max(min, Math.min(max, width)));
|
|
270
|
+
}
|
|
271
|
+
/** Stable, locale-aware sort shared by the React adapter and custom element. */
|
|
272
|
+
export function sortDataGridItems(items, columns, sort, locale) {
|
|
273
|
+
const sorts = (Array.isArray(sort) ? sort : sort ? [sort] : []).filter((entry, index, all) => all.findIndex(candidate => candidate.columnId === entry.columnId) === index && columns.some(column => column.id === entry.columnId && column.sortable !== false));
|
|
274
|
+
if (!sorts.length)
|
|
275
|
+
return [...items];
|
|
276
|
+
const sortColumns = sorts.flatMap(entry => {
|
|
277
|
+
const column = columns.find(candidate => candidate.id === entry.columnId);
|
|
278
|
+
return column ? [{ sort: entry, column }] : [];
|
|
279
|
+
});
|
|
280
|
+
const collator = new Intl.Collator(locale?.trim() || undefined, { numeric: true, sensitivity: "base" });
|
|
281
|
+
return items.map((item, index) => ({ item, index, values: sortColumns.map(({ column }) => column.value ? column.value(item) : item?.[column.id]) })).sort((a, b) => {
|
|
282
|
+
for (let index = 0; index < sortColumns.length; index++) {
|
|
283
|
+
const { sort: direction } = sortColumns[index];
|
|
284
|
+
const rawLeft = a.values[index], rawRight = b.values[index];
|
|
285
|
+
const left = rawLeft instanceof Date ? rawLeft.valueOf() : rawLeft;
|
|
286
|
+
const right = rawRight instanceof Date ? rawRight.valueOf() : rawRight;
|
|
287
|
+
const result = left == null ? right == null ? 0 : -1 : right == null ? 1
|
|
288
|
+
: typeof left === "number" && typeof right === "number" ? left - right : collator.compare(String(left), String(right));
|
|
289
|
+
if (result)
|
|
290
|
+
return direction.direction === "ascending" ? result : -result;
|
|
291
|
+
}
|
|
292
|
+
return a.index - b.index;
|
|
293
|
+
}).map(entry => entry.item);
|
|
294
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type DataGridCellEdit, type DataGridColumn, type DataGridDataSource, type DataGridColumnSizingOptions, type DataGridFocusMode, type DataGridSelectionAppearance, type DataGridSelectionModel } from "./data-grid-model.js";
|
|
2
|
+
export type DataGridCellRenderer<T = unknown> = (item: T, column: DataGridColumn<T>, rowIndex: number) => Node | string | number | null;
|
|
3
|
+
export type DataGridRowHeightGetter<T = unknown> = (item: T, rowIndex: number) => number;
|
|
4
|
+
export type DataGridCellsEditHandler = (edits: readonly DataGridCellEdit[]) => void | Promise<void>;
|
|
5
|
+
export type DataGridSourceSelectionHandler = (selection: DataGridSelectionModel, selectedItems: readonly unknown[]) => void;
|
|
6
|
+
export interface SharePointDataGridElement extends HTMLElement {
|
|
7
|
+
items: unknown[];
|
|
8
|
+
dataSource?: DataGridDataSource;
|
|
9
|
+
columns: DataGridColumn[];
|
|
10
|
+
focusMode: DataGridFocusMode;
|
|
11
|
+
selectionAppearance: DataGridSelectionAppearance;
|
|
12
|
+
subtleSelection: boolean;
|
|
13
|
+
columnSizingOptions: DataGridColumnSizingOptions;
|
|
14
|
+
autoFitColumns: boolean;
|
|
15
|
+
containerWidthOffset: number;
|
|
16
|
+
refreshDataSource(): void;
|
|
17
|
+
onDataSourceGroupToggle?: (groupKey: string, expanded: boolean) => void;
|
|
18
|
+
selectionModel: DataGridSelectionModel;
|
|
19
|
+
onDataSourceSelectionChange?: DataGridSourceSelectionHandler;
|
|
20
|
+
getRowHeight?: DataGridRowHeightGetter;
|
|
21
|
+
onCellsEdit?: DataGridCellsEditHandler;
|
|
22
|
+
}
|
|
23
|
+
declare global {
|
|
24
|
+
interface HTMLElementTagNameMap {
|
|
25
|
+
"spui-data-grid": SharePointDataGridElement;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Accessible, virtualized grid engine for non-React and React consumers. */
|
|
29
|
+
export declare function defineDataGrid(): void;
|