diff --git a/documentation/ag-grid-docs/src/components/reference-documentation/components/Property.tsx b/documentation/ag-grid-docs/src/components/reference-documentation/components/Property.tsx index 5506a30c008..eb49034bd53 100644 --- a/documentation/ag-grid-docs/src/components/reference-documentation/components/Property.tsx +++ b/documentation/ag-grid-docs/src/components/reference-documentation/components/Property.tsx @@ -260,9 +260,13 @@ export const Property: FunctionComponent<{ return ( -
+
-
+
diff --git a/documentation/ag-grid-docs/src/components/reference-documentation/components/Section.tsx b/documentation/ag-grid-docs/src/components/reference-documentation/components/Section.tsx index 310b2eb6fa3..497219c23e6 100644 --- a/documentation/ag-grid-docs/src/components/reference-documentation/components/Section.tsx +++ b/documentation/ag-grid-docs/src/components/reference-documentation/components/Section.tsx @@ -198,6 +198,7 @@ export const Section: FunctionComponent = ({ /> )} diff --git a/documentation/ag-grid-docs/src/components/reference-documentation/types.d.ts b/documentation/ag-grid-docs/src/components/reference-documentation/types.d.ts index ddbf4bbc8ba..6ac3ce36511 100644 --- a/documentation/ag-grid-docs/src/components/reference-documentation/types.d.ts +++ b/documentation/ag-grid-docs/src/components/reference-documentation/types.d.ts @@ -14,8 +14,6 @@ interface MetaTag { }; type?: string; isEvent?: boolean; - /** Suppress the missing property check. Needed for events as they are dynamic and so do not appear in src code */ - suppressMissingPropCheck?: true; } export type DocEntryMap = Record; type DocEntry = { @@ -173,9 +171,6 @@ export interface Config { headerLevel?: number; /** Set the margin-bottom value to override the default of 3em */ overrideBottomMargin?: string; - /** Suppress the missing property check. Needed for events as they are dynamic and so do not appear in src code */ - suppressMissingPropCheck?: true; - /** A regular expression limiting the names that should appear */ namePattern: string; diff --git a/documentation/ag-grid-docs/src/components/reference-documentation/utils/getPropertiesFromSource.ts b/documentation/ag-grid-docs/src/components/reference-documentation/utils/getPropertiesFromSource.ts index b2c62d21cd5..73aa0708b8f 100644 --- a/documentation/ag-grid-docs/src/components/reference-documentation/utils/getPropertiesFromSource.ts +++ b/documentation/ag-grid-docs/src/components/reference-documentation/utils/getPropertiesFromSource.ts @@ -14,7 +14,7 @@ export const getPropertiesFromSource = async ({ sources: string[]; }) => { const sources = source ? [source] : sourcesProp; - const propertiesFromFilesPromises = sources.map(async (s: string) => { + const fileEntryPromises = sources.map(async (s: string) => { // NOTE: Need to remove `.json` for getEntry const fileName = s.replace('.json', ''); const fileEntry = await getEntry('apiDocumentation', fileName); @@ -26,35 +26,45 @@ export const getPropertiesFromSource = async ({ } else { throw new Error(message); } - } else { - return fileEntry.data; } + return fileEntry; }); - const propertiesFromFiles = (await Promise.all(propertiesFromFilesPromises)).filter(Boolean); + const fileEntries = await Promise.all(fileEntryPromises); - const propertyConfigs = propertiesFromFiles - .map((p) => { - const config = p['_config_']; - if (!config) { - // eslint-disable-next-line no-console - console.warn(`ApiDocumentation: _config_ property missing from source ${sources.join()}.`); - } - return config; - }) - .filter(Boolean); - const codeConfigEntries = propertyConfigs - .map((config) => config.codeSrc) - .map((fileName) => { - const referenceFileName = `reference/${fileName}`; - const file = getJsonFile(referenceFileName); - return [fileName, file]; - }); - const codeConfigs = Object.fromEntries(codeConfigEntries); + const propertiesFromFiles: any[] = []; + const propertyConfigs: any[] = []; + const codeConfigs: Record = {}; + + for (let i = 0; i < sources.length; i++) { + const fileEntry = fileEntries[i]; + if (!fileEntry) { + continue; + } - // Validate that theming-api/properties.json keys match the theming-api.AUTO.json keys - // Only run when actually processing the theming-api source - if (sources.some((s) => s.includes('theming-api'))) { - validateThemingApiProperties(propertiesFromFiles, codeConfigs); + const s = sources[i]; + const propsFile = fileEntry.data; + propertiesFromFiles.push(propsFile); + + const config = propsFile['_config_']; + if (!config) { + // eslint-disable-next-line no-console + console.warn(`ApiDocumentation: _config_ property missing from source ${s}.`); + continue; + } + propertyConfigs.push(config); + + const codeSrc = config.codeSrc; + if (codeSrc && !(codeSrc in codeConfigs)) { + codeConfigs[codeSrc] = getJsonFile(`reference/${codeSrc}`); + } + + if (config.validate) { + const codeConfig = codeConfigs[codeSrc]; + if (!codeConfig) { + throw new Error(`${s} codeSrc file not found: ${codeSrc}`); + } + validateDocumentedProperties(propsFile, codeConfig, s); + } } return { @@ -65,32 +75,54 @@ export const getPropertiesFromSource = async ({ }; }; -function validateThemingApiProperties(properties: any[], codeConfigs: any) { - const codeSrc = 'theming-api.AUTO.json'; - const propsFile = properties.find((p) => p['_config_']?.codeSrc === codeSrc); - if (!propsFile) { - throw new Error(`No properties.json with codeSrc: "${codeSrc}"`); - } - const codeConfig = codeConfigs[codeSrc]; - if (!codeConfig) { - throw new Error(`Theme params codeSrc file not found: ${codeSrc}`); - } - const codeKeys = new Set(Object.keys(codeConfig)); - const propsKeys = Object.entries(propsFile) +function validateDocumentedProperties(propsFile: any, codeConfig: any, source: string) { + const config = propsFile['_config_']; + const codeSrc = config.codeSrc; + const undocumentedProperties = config.undocumentedProperties ?? {}; + + const keysToDocument = new Set( + Object.keys(codeConfig).filter((k) => { + if (k in undocumentedProperties) { + return false; + } + const entry = codeConfig[k]; + if (entry?.meta?.tags?.some((t: any) => t.name === 'deprecated')) { + return false; + } + if (config.excludeEvents && entry?.meta?.isEvent) { + return false; + } + if (config.onlyEvents) { + if (!entry?.meta?.isEvent) { + return false; + } + // Events like onCellClicked are the grid option callback form; + // event docs use the unprefixed name (cellClicked) + if (/^on[A-Z]/.test(k)) { + return false; + } + } + return true; + }) + ); + + const documentedKeys = Object.entries(propsFile) .filter(([k]) => k !== '_config_') .flatMap(([, section]) => Object.keys(section as object).filter((k) => k !== 'meta')); - const missing = propsKeys.filter((k) => !codeKeys.has(k)); - const extra = [...codeKeys].filter((k) => !propsKeys.includes(k)); - if (missing.length || extra.length) { + + const stale = documentedKeys.filter((k) => !keysToDocument.has(k)); + const undocumented = [...keysToDocument].filter((k) => !documentedKeys.includes(k)); + + if (stale.length || undocumented.length) { const msgs: string[] = []; - if (missing.length) { + if (stale.length) { msgs.push( - `These theme params are documented in theming-api/properties.json but not in the API (checking ${codeSrc}): ${missing.join(', ')}` + `These ${source} keys are documented but not in the API (checking ${codeSrc}): ${stale.join(', ')}` ); } - if (extra.length) { + if (undocumented.length) { msgs.push( - `These theme params are present in the API (checking ${codeSrc}) but not documented in theming-api/properties.json: ${extra.join(', ')}` + `These ${source} keys are present in the API (checking ${codeSrc}) but not documented: ${undocumented.join(', ')}` ); } throw new Error(msgs.join('\n')); diff --git a/documentation/ag-grid-docs/src/content/api-documentation/aggregation-filtering/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/aggregation-filtering/properties.json deleted file mode 100644 index 37e9b8ba4c9..00000000000 --- a/documentation/ag-grid-docs/src/content/api-documentation/aggregation-filtering/properties.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "_config_": { - "codeSrc": "row-node.AUTO.json" - }, - "rowNodeAttributes": { - "meta": { - "displayName": "RowNode attributes" - }, - "level": {}, - "group": {}, - "leafGroup": {}, - "rowGroupColumn": {} - } -} diff --git a/documentation/ag-grid-docs/src/content/api-documentation/column-events/events.json b/documentation/ag-grid-docs/src/content/api-documentation/column-events/events.json index 42a50990381..d06f994028a 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/column-events/events.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/column-events/events.json @@ -5,8 +5,7 @@ "events": { "meta": { "displayName": "Events on Column", - "isEvent": true, - "suppressMissingPropCheck": true + "isEvent": true }, "filterActiveChanged": { "description": "The filter active value has changed.", diff --git a/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/properties.json index 2f8b617c93d..f206d665df9 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/properties.json @@ -1,6 +1,11 @@ { "_config_": { - "codeSrc": "columnGroup.AUTO.json" + "codeSrc": "columnGroup.AUTO.json", + "validate": true, + "undocumentedProperties": { + "getDefinition": "Alias of getColGroupDef, the specific version is preferred", + "getUniqueId": "Alias of getColId on the underlying columns" + } }, "ColumnGroup": { "getColGroupDef": {}, @@ -15,6 +20,14 @@ "isExpanded": {}, "isPadding": {}, "getPaddingLevel": {}, - "isColumn": {} + "isColumn": {}, + "getActualWidth": {}, + "getMinWidth": {}, + "getLeft": {}, + "getColumnGroupShow": {}, + "getParent": {}, + "isEmptyGroup": {}, + "isMoving": {}, + "getPinned": {} } } diff --git a/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/provided-properties.json b/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/provided-properties.json index 2d9d9b25aac..05a4b8239a8 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/provided-properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/column-object-group/provided-properties.json @@ -1,6 +1,10 @@ { "_config_": { - "codeSrc": "providedColumnGroup.AUTO.json" + "codeSrc": "providedColumnGroup.AUTO.json", + "validate": true, + "undocumentedProperties": { + "getId": "Alias of getGroupId" + } }, "ProvidedColumnGroup": { "getOriginalParent": {}, @@ -12,6 +16,8 @@ "getChildren": {}, "getColGroupDef": {}, "getLeafColumns": {}, - "isColumn": {} + "isColumn": {}, + "isVisible": {}, + "getColumnGroupShow": {} } } diff --git a/documentation/ag-grid-docs/src/content/api-documentation/column-object/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/column-object/properties.json index 83060b8a42c..b477487d012 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/column-object/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/column-object/properties.json @@ -1,6 +1,13 @@ { "_config_": { - "codeSrc": "column.AUTO.json" + "codeSrc": "column.AUTO.json", + "validate": true, + "undocumentedProperties": { + "getDefinition": "Alias of getColDef, the specific version is preferred", + "getUniqueId": "Alias of getColId", + "getId": "Alias of getColId", + "getRowSpan": "Same functionality as rowSpan" + } }, "definitions": { "meta": { @@ -155,5 +162,11 @@ "displayName": "Keyboard" }, "isSuppressNavigable": {} + }, + "formulas": { + "meta": { + "displayName": "Formulas" + }, + "isAllowFormula": {} } } diff --git a/documentation/ag-grid-docs/src/content/api-documentation/column-properties/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/column-properties/properties.json index 1a5544722ec..ebe99bfda22 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/column-properties/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/column-properties/properties.json @@ -1,6 +1,13 @@ { "_config_": { - "codeSrc": "column-options.AUTO.json" + "codeSrc": "column-options.AUTO.json", + "validate": true, + "undocumentedProperties": { + "pivotKeys": "Internal use only, doc comment says 'Never set this'", + "pivotValueColumn": "Internal use only, doc comment says 'Never set this'", + "pivotTotalColumnIds": "Internal use only, doc comment says 'Never set this'", + "rowSpan": "Old API, not deprecated, but not encouraged either" + } }, "columns": { "meta": { @@ -466,6 +473,8 @@ "cellEditorPopup": {}, "cellEditorPopupPosition": {}, "singleClickEdit": {}, + "groupRowEditable": {}, + "groupRowValueSetter": {}, "useValueParserForImport": { "more": { "name": "Using Value Parsers with Other Grid Features", diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json index 79e9f8127b1..914a3bd29af 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json @@ -1,6 +1,16 @@ { "_config_": { - "codeSrc": "grid-api.AUTO.json" + "codeSrc": "grid-api.AUTO.json", + "validate": true, + "undocumentedProperties": { + "sortChanged": "TODO: undocumented property, to resolve in AG-16879", + "flushAllAnimationFrames": "TODO: undocumented property, to resolve in AG-16879", + "getColumnDef": "TODO: undocumented property, to resolve in AG-16879", + "filterChanged": "TODO: undocumented property, to resolve in AG-16879", + "getInfiniteRowCount": "TODO: undocumented property, to resolve in AG-16879", + "flushServerSideAsyncTransactions": "TODO: undocumented property, to resolve in AG-16879", + "dispatchEvent": "TODO: undocumented property, to resolve in AG-16879" + } }, "gridOptions": { "meta": { diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-events/events.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-events/events.json index fd2cc5721dc..0177f6db7ab 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-events/events.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-events/events.json @@ -1,6 +1,15 @@ { "_config_": { - "codeSrc": "grid-options.AUTO.json" + "codeSrc": "grid-options.AUTO.json", + "validate": true, + "onlyEvents": true, + "undocumentedProperties": { + "bulkEditingStarted": "TODO: undocumented property, to resolve in AG-16879", + "bulkEditingStopped": "TODO: undocumented property, to resolve in AG-16879", + "dragCancelled": "TODO: undocumented property, to resolve in AG-16879", + "rowResizeStarted": "TODO: undocumented property, to resolve in AG-16879", + "rowResizeEnded": "TODO: undocumented property, to resolve in AG-16879" + } }, "accessories": { "meta": { diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json index a74b9e01619..d78e3f4bd04 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json @@ -1,6 +1,12 @@ { "_config_": { - "codeSrc": "grid-options.AUTO.json" + "codeSrc": "grid-options.AUTO.json", + "validate": true, + "excludeEvents": true, + "undocumentedProperties": { + "renderingMode": "React-only, so needs to be omitted from grid-options/properties.json (it is documented in grid-options/react-grid-options.json)", + "enableGroupEdit": "AG-2995 - 'Remove the grid option enableGroupEdit from docs API page to discourage its use'" + } }, "accessories": { "meta": { @@ -1152,6 +1158,12 @@ "url": "./pivoting-totals/#row-totals" } }, + "pivotColumnGroupTotals": { + "more": { + "name": "Pivot Column Group Totals", + "url": "./pivoting-totals/#column-group-totals" + } + }, "pivotSuppressAutoColumn": { "description": "If `true`, the grid will not swap in the grouping column when pivoting. Useful if pivoting using [Server Side Row Model](./server-side-model/) or [Viewport Row Model](./viewport/) and you want full control of all columns including the group column." }, @@ -1265,7 +1277,8 @@ "name": "Row Spanning", "url": "./row-spanning/" } - } + }, + "suppressRowTransform": {} }, "rowDragging": { "meta": { @@ -1358,6 +1371,12 @@ "name": "Embedded Full Width Rows", "url": "./full-width-rows/#embedded-full-width-rows" } + }, + "isFullWidthRow": { + "more": { + "name": "Full Width Rows", + "url": "./full-width-rows/" + } } }, "rowGrouping": { @@ -1536,6 +1555,7 @@ "url": "./tree-data/" } }, + "treeDataDisplayType": {}, "autoGroupColumnDef": { "more": { "name": "Group Column Configuration", diff --git a/documentation/ag-grid-docs/src/content/api-documentation/row-events/events.json b/documentation/ag-grid-docs/src/content/api-documentation/row-events/events.json index b6212f330e4..b309ca94cc9 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/row-events/events.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/row-events/events.json @@ -1,7 +1,6 @@ { "_config_": { - "codeSrc": "row-node.AUTO.json", - "suppressMissingPropCheck": true + "codeSrc": "row-node.AUTO.json" }, "rowNodeEvents": { "meta": { diff --git a/documentation/ag-grid-docs/src/content/api-documentation/row-object/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/row-object/properties.json index 7391d53586d..25c9a8bbbb0 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/row-object/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/row-object/properties.json @@ -1,6 +1,7 @@ { "_config_": { - "codeSrc": "row-node.AUTO.json" + "codeSrc": "row-node.AUTO.json", + "validate": true }, "display": { "meta": { diff --git a/documentation/ag-grid-docs/src/content/api-documentation/theming-api/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/theming-api/properties.json index fda3e4bc7b6..232293261b5 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/theming-api/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/theming-api/properties.json @@ -1,6 +1,7 @@ { "_config_": { "codeSrc": "theming-api.AUTO.json", + "validate": true, "codeWordsMode": "cssVariable" }, "colorsAndDarkMode": { diff --git a/documentation/ag-grid-docs/src/content/interface-documentation/excel-export-api/excel-api.json b/documentation/ag-grid-docs/src/content/interface-documentation/excel-export-api/excel-api.json index 66c21869176..0631fe1b1b9 100644 --- a/documentation/ag-grid-docs/src/content/interface-documentation/excel-export-api/excel-api.json +++ b/documentation/ag-grid-docs/src/content/interface-documentation/excel-export-api/excel-api.json @@ -48,8 +48,7 @@ }, "ExcelHeaderFooter": { "meta": { - "description": "Properties available on the `ExcelHeaderFooter` interface. At least one of header or footer is required or both.", - "suppressMissingPropCheck": true + "description": "Properties available on the `ExcelHeaderFooter` interface. At least one of header or footer is required or both." }, "header": { "description": "An array of maximum 3 items (`Left`, `Center`, `Right`), containing header configurations.", diff --git a/documentation/ag-grid-docs/src/content/matrix-table/row-models.json b/documentation/ag-grid-docs/src/content/matrix-table/row-models.json index 804c0554363..b60c7305698 100644 --- a/documentation/ag-grid-docs/src/content/matrix-table/row-models.json +++ b/documentation/ag-grid-docs/src/content/matrix-table/row-models.json @@ -114,9 +114,9 @@ { "feature": "[Row Spanning](./row-spanning)", "clientSide": true, - "infinite": true, + "infinite": false, "serverSide": true, - "viewport": true + "viewport": false }, { "feature": "[Column Pinning](./column-pinning)", diff --git a/documentation/update-algolia-indices/src/config.ts b/documentation/update-algolia-indices/src/config.ts index c366b7f389a..8c524c73b36 100644 --- a/documentation/update-algolia-indices/src/config.ts +++ b/documentation/update-algolia-indices/src/config.ts @@ -54,8 +54,9 @@ export const TYPE_PROPERTY_INDEXING: Record { // and shouldn't be searchable text = text.replace(/.*?<\/style>/gs, ''); + // Remove tags without replacing with space, so that camelCase words + // split for display (e.g. processCellCallback) are rejoined + text = text.replace(//gi, ''); + // strip all HTML tags so that names and content of attributes aren't searchable text = text.replace(/<.*?>/gs, ' '); @@ -57,12 +61,14 @@ export const getAllDocPages = (): FlattenedMenuItem[] => { const apiMenu = getApiMenuData(); pageRank = 0; - const allSections = [...docsMenu.sections, ...apiMenu.sections]; - - const flattenedMenuItems = getFlattenedMenuItems(allSections); - const flattenedDocMigrationItems = getFlattenedDocMigrationItems(); - - return [...flattenedMenuItems, ...flattenedDocMigrationItems]; + return [ + ...getFlattenedMenuItems(docsMenu.sections), + ...getFlattenedMenuItems(apiMenu.sections).map((item) => ({ + ...item, + isApiPage: true, + })), + ...getFlattenedDocMigrationItems(), + ]; }; function getHeadingContent(heading: Element) { @@ -146,6 +152,39 @@ export const parseDocPage = async (item: FlattenedMenuItem) => { continue; } + // Process API reference tables by extracting each property as a + // separate record with the property name as a subHeading + if (currentTag.hasAttribute?.('data-api-reference-table')) { + if (item.isApiPage) { + continue; + } + for (const prop of currentTag.querySelectorAll('[data-api-property]')) { + const nameEl = prop.querySelector('[data-api-property-name]'); + if (!nameEl) continue; + const propertyName = getHeadingContent(nameEl); + const anchor = nameEl.id; + const descEl = prop.querySelector('[data-api-property-description]'); + const descHtml = descEl?.innerHTML ?? ''; + const descCodeWords = extractCodeWords(descHtml); + const positionInPage = position++; + const propertyPath = anchor ? `${path}#${anchor}` : path; + records.push({ + source: 'docs', + objectID: `${propertyPath}:${positionInPage}`, + breadcrumb, + title: pageTitle || title, + heading: [subHeading || heading, propertyName].filter(Boolean).join(' > ') || undefined, + subHeading: propertyName, + path: propertyPath, + text: truncateAtWordBoundary(cleanContents(descHtml), 120, 250), + codeWords: descCodeWords.length > 0 ? descCodeWords : undefined, + rank, + positionInPage, + }); + } + continue; + } + switch (currentTag.nodeName) { // split records based on H2 and H3 tags case 'H2': { @@ -164,9 +203,10 @@ export const parseDocPage = async (item: FlattenedMenuItem) => { break; } - case 'DIV': { + case 'DIV': + case 'ASTRO-ISLAND': { createPreviousRecord(); - // process content inside div containers + // process content inside div/astro-island containers recursivelyParseContent(currentTag.firstChild as Element | null); break; } @@ -244,6 +284,7 @@ export interface FlattenedMenuItem { path: string; rank: number; breadcrumb: string; + isApiPage?: boolean; } const getFlattenedMenuItems = ( diff --git a/documentation/update-algolia-indices/src/utils/output.ts b/documentation/update-algolia-indices/src/utils/output.ts index 75ab30afc16..339e378f124 100644 --- a/documentation/update-algolia-indices/src/utils/output.ts +++ b/documentation/update-algolia-indices/src/utils/output.ts @@ -62,6 +62,8 @@ export const updateAlgolia = async (indexName: string, records: Record = ColDef { deltaSort?: boolean; /** + * Specifies how tree data should be displayed. + * + * The options are: + * + * - `'auto'`: group column automatically added by the grid. + * - `'custom'`: informs the grid that group columns will be provided. + * @agModule `TreeDataModule` */ treeDataDisplayType?: TreeDataDisplayType; diff --git a/packages/ag-grid-community/src/filter/provided/date/dateFilter.ts b/packages/ag-grid-community/src/filter/provided/date/dateFilter.ts index d09d20de3ea..4db3e85db05 100644 --- a/packages/ag-grid-community/src/filter/provided/date/dateFilter.ts +++ b/packages/ag-grid-community/src/filter/provided/date/dateFilter.ts @@ -9,7 +9,7 @@ import { _warn } from '../../../validation/logging'; import type { FilterLocaleTextKey } from '../../filterLocaleText'; import type { ICombinedSimpleModel, Tuple } from '../iSimpleFilter'; import { SimpleFilter } from '../simpleFilter'; -import { removeItems } from '../simpleFilterUtils'; +import { getNumberOfInputs, removeItems } from '../simpleFilterUtils'; import { DateCompWrapper } from './dateCompWrapper'; import { DEFAULT_DATE_FILTER_OPTIONS } from './dateFilterConstants'; import { mapValuesFromDateFilterModel } from './dateFilterUtils'; @@ -104,9 +104,11 @@ export class DateFilter extends SimpleFilter= 2 ? getRangeValidityMessageKey(fromDate, toDate, isFrom) : null; const message = localeKey ? this.translate(localeKey, [String(isFrom ? toDate : fromDate)]) : ''; // FF seems to handle cursors/focus sufficiently well for the validation to be left as synchronous. @@ -356,7 +358,7 @@ export class DateFilter extends SimpleFilter Validations = () => { enableRangeSelection: { required: [true] }, }, }, + enableCellSpan: { + supportedRowModels: ['clientSide', 'serverSide'], + }, enableRangeSelection: { dependencies: { rowDragEntireRow: { required: [false, undefined] }, diff --git a/packages/ag-grid-vue3/src/components/utils.ts b/packages/ag-grid-vue3/src/components/utils.ts index 06f0f70fdaf..8733e6bcdcd 100644 --- a/packages/ag-grid-vue3/src/components/utils.ts +++ b/packages/ag-grid-vue3/src/components/utils.ts @@ -1638,7 +1638,14 @@ export interface Props { * @default false */ deltaSort?: boolean, - /**/ + /** Specifies how tree data should be displayed. + * + * The options are: + * + * - `'auto'`: group column automatically added by the grid. + * - `'custom'`: informs the grid that group columns will be provided. + * @agModule `TreeDataModule` + */ treeDataDisplayType?: TreeDataDisplayType, /** @initial */ diff --git a/testing/behavioural/src/filters/date-filter-range-validation.test.ts b/testing/behavioural/src/filters/date-filter-range-validation.test.ts index 7acee2d2b84..a4e732d4e5b 100644 --- a/testing/behavioural/src/filters/date-filter-range-validation.test.ts +++ b/testing/behavioural/src/filters/date-filter-range-validation.test.ts @@ -1,6 +1,7 @@ -import { getByTestId, waitFor } from '@testing-library/dom'; +import { getAllByTestId, getByTestId, waitFor } from '@testing-library/dom'; import { userEvent } from '@testing-library/user-event'; +import type { DateFilterModel } from 'ag-grid-community'; import { ClientSideRowModelModule, DateFilterModule, @@ -101,3 +102,187 @@ describe('Number Range Filter', () => { expect(toNumberInput).toHaveAttribute('aria-invalid', 'false'); }); }); + +async function selectFilterOption(gridDiv: HTMLElement, userSession: any, optionText: string): Promise { + const pickerDisplay = getAllByTestId( + gridDiv, + agTestIdFor.filterInstancePickerDisplay({ source: 'column-filter' }) + )[0]; + await userSession.click(pickerDisplay); + + await asyncSetTimeout(0); + + const listItems = document.querySelectorAll('.ag-list-item'); + let targetItem: Element | null = null; + listItems.forEach((item) => { + if (item.textContent?.trim() === optionText) { + targetItem = item; + } + }); + expect(targetItem).not.toBeNull(); + await userSession.click(targetItem!); + + await asyncSetTimeout(0); +} + +describe('Date Range Filter', () => { + const gridsManager = new TestGridsManager({ + modules: [DateFilterModule, ClientSideRowModelModule, TextFilterModule], + }); + + beforeAll(() => setupAgTestIds()); + afterEach(() => gridsManager.reset()); + + test('Switching from inRange to equals clears range validation on the from input', async () => { + const userSession = userEvent.setup(); + + const api = await gridsManager.createGridAndWait('grid1', { + columnDefs: [ + { + field: 'date', + filter: 'agDateColumnFilter', + filterParams: { + filterOptions: ['inRange', 'equals'], + }, + }, + ], + rowData: [{ date: '2024-01-15' }, { date: '2024-06-15' }, { date: '2024-12-15' }], + }); + + const gridDiv = getGridElement(api)! as HTMLElement; + + await asyncSetTimeout(0); + + // Open the filter popup + const filterBtn = getByTestId(gridDiv, agTestIdFor.headerFilterButton('date')); + await userSession.click(filterBtn); + + await asyncSetTimeout(0); + + // Enter dates into the inRange inputs: from=2024-01-15, to=2024-06-15 + const fromDateInput = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 0 }) + ); + const toDateInput = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 1 }) + ); + + // Use fireEvent to set date values (userEvent.type doesn't work well with date inputs) + fromDateInput.valueAsDate = new Date('2024-01-15'); + fromDateInput.dispatchEvent(new Event('input', { bubbles: true })); + fromDateInput.dispatchEvent(new Event('change', { bubbles: true })); + + toDateInput.valueAsDate = new Date('2024-06-15'); + toDateInput.dispatchEvent(new Event('input', { bubbles: true })); + toDateInput.dispatchEvent(new Event('change', { bubbles: true })); + + await asyncSetTimeout(0); + + // Both inputs should be valid (from < to) + expect(fromDateInput.validity.valid).toBe(true); + expect(toDateInput.validity.valid).toBe(true); + + await waitFor(() => { + const model = api.getFilterModel()?.date as DateFilterModel; + expect(model).toBeTruthy(); + expect(model.type).toBe('inRange'); + }); + + // Switch to "equals" via the filter type picker + await selectFilterOption(gridDiv, userSession, 'Equals'); + + // Now the filter is "equals" - the to input is hidden but still has its value. + // Change the from date to match what was in the to date. + const fromDateInputEquals = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter' }) + ); + + fromDateInputEquals.valueAsDate = new Date('2024-06-15'); + fromDateInputEquals.dispatchEvent(new Event('input', { bubbles: true })); + fromDateInputEquals.dispatchEvent(new Event('change', { bubbles: true })); + + await asyncSetTimeout(0); + + // The from input should be valid - no range validation should apply for "equals" + expect(fromDateInputEquals.validity.valid).toBe(true); + }); + + test('Switching from equals back to inRange re-enables range validation', async () => { + const userSession = userEvent.setup(); + + const api = await gridsManager.createGridAndWait('grid1', { + columnDefs: [ + { + field: 'date', + filter: 'agDateColumnFilter', + filterParams: { + filterOptions: ['inRange', 'equals'], + }, + }, + ], + rowData: [{ date: '2024-01-15' }, { date: '2024-06-15' }, { date: '2024-12-15' }], + }); + + const gridDiv = getGridElement(api)! as HTMLElement; + + await asyncSetTimeout(0); + + // Open the filter popup + const filterBtn = getByTestId(gridDiv, agTestIdFor.headerFilterButton('date')); + await userSession.click(filterBtn); + + await asyncSetTimeout(0); + + // Enter valid inRange dates: from=2024-01-15, to=2024-06-15 + const fromDateInput = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 0 }) + ); + const toDateInput = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 1 }) + ); + + fromDateInput.valueAsDate = new Date('2024-01-15'); + fromDateInput.dispatchEvent(new Event('input', { bubbles: true })); + toDateInput.valueAsDate = new Date('2024-06-15'); + toDateInput.dispatchEvent(new Event('input', { bubbles: true })); + + await asyncSetTimeout(0); + + // Switch to "equals" + await selectFilterOption(gridDiv, userSession, 'Equals'); + + // Change the from date to be after what was in the to date + const fromDateInputEquals = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter' }) + ); + + fromDateInputEquals.valueAsDate = new Date('2024-12-15'); + fromDateInputEquals.dispatchEvent(new Event('input', { bubbles: true })); + + await asyncSetTimeout(0); + + // Valid in "equals" mode - no range validation + expect(fromDateInputEquals.validity.valid).toBe(true); + + // Switch back to "inRange" - the from date (2024-12-15) is now after the to date (2024-06-15) + await selectFilterOption(gridDiv, userSession, 'Between'); + + // Trigger validation by interacting with the from input + const fromDateInputRange = getByTestId( + gridDiv, + agTestIdFor.dateFilterInstanceInput({ source: 'column-filter', index: 0 }) + ); + fromDateInputRange.dispatchEvent(new Event('focusin', { bubbles: true })); + + await asyncSetTimeout(0); + + // Range validation should now be active again - from > to is invalid + expect(fromDateInputRange.validity.valid).toBe(false); + }); +}); diff --git a/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts b/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts index 53d2100c26a..9eda4f62845 100644 --- a/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts +++ b/testing/behavioural/src/grouping-data/grouping-show-columns-when-expanded.test.ts @@ -1,5 +1,5 @@ import type { GridApi } from 'ag-grid-community'; -import { ClientSideRowModelModule, QuickFilterModule } from 'ag-grid-community'; +import { ClientSideRowModelModule, QuickFilterModule, getGridElement } from 'ag-grid-community'; import { PivotModule, RowGroupingModule } from 'ag-grid-enterprise'; import { GridRows, TestGridsManager, cachedJSONObjects } from '../test-utils'; @@ -11,6 +11,19 @@ function getVisibleAutoGroupColIds(api: GridApi): string[] { .map((col) => col.getColId()); } +/** Returns col-id attributes from the first data row's cells in DOM order */ +function getCellColIdsFromDom(api: GridApi): string[] { + const gridElement = getGridElement(api); + if (!gridElement) { + return []; + } + const firstRow = gridElement.querySelector('[role="row"][row-index="0"]'); + if (!firstRow) { + return []; + } + return Array.from(firstRow.querySelectorAll('[role="gridcell"]')).map((cell) => cell.getAttribute('col-id') ?? ''); +} + describe('ag-grid groupHideColumnsUntilExpanded', () => { const gridsManager = new TestGridsManager({ modules: [ClientSideRowModelModule, RowGroupingModule, QuickFilterModule], @@ -865,6 +878,40 @@ describe('ag-grid groupHideColumnsUntilExpanded', () => { expect(getVisibleAutoGroupColIds(api)).toEqual(['ag-Grid-AutoColumn']); }); + test('DOM order of cells matches display order when group columns are revealed', async () => { + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true }, + { field: 'year', rowGroup: true }, + { field: 'athlete' }, + { field: 'gold' }, + ], + groupDisplayType: 'multipleColumns', + groupHideColumnsUntilExpanded: true, + groupDefaultExpanded: 0, + ensureDomOrder: true, + rowData: twoLevelRowData, + getRowId: (params) => params.data.id, + }); + + // Initially only the country auto-group column is visible + expect(getCellColIdsFromDom(api)).toEqual(['ag-Grid-AutoColumn-country', 'country', 'year', 'athlete', 'gold']); + + // Expand Ireland - year auto-group column becomes visible + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // The newly revealed auto-group-year column should appear in the correct DOM position + // (after auto-group-country, before the data columns) + expect(getCellColIdsFromDom(api)).toEqual([ + 'ag-Grid-AutoColumn-country', + 'ag-Grid-AutoColumn-year', + 'country', + 'year', + 'athlete', + 'gold', + ]); + }); + test('has no effect with groupRows display type', async () => { const api = gridsManager.createGrid('myGrid', { columnDefs: [