@linzjs/step-ag-grid 13.3.1 → 13.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/README.md CHANGED
@@ -65,6 +65,7 @@ import {
65
65
  // Only required for LINZ themes otherwise import the default theme from ag-grid
66
66
  import "@linzjs/step-ag-grid/dist/GridTheme.scss";
67
67
  import "@linzjs/step-ag-grid/dist/index.css";
68
+ import { GridFilterDownloadCsvButton } from "./GridFilterDownloadCsvButton";
68
69
 
69
70
  const GridDemo = () => {
70
71
  interface ITestRow {
@@ -80,6 +81,7 @@ const GridDemo = () => {
80
81
  headerName: "Id",
81
82
  initialWidth: 65,
82
83
  maxWidth: 85,
84
+ export: false,
83
85
  }),
84
86
  GridCell({
85
87
  field: "name",
@@ -113,7 +115,7 @@ const GridDemo = () => {
113
115
  {
114
116
  multiEdit: true,
115
117
  editorParams: {
116
- message: async ({ selectedRows }) => {
118
+ message: async ({selectedRows}) => {
117
119
  return `There are ${selectedRows.length} row(s) selected`;
118
120
  },
119
121
  },
@@ -136,25 +138,26 @@ const GridDemo = () => {
136
138
  <GridContextProvider>
137
139
  <GridWrapper>
138
140
  <GridFilters>
139
- <GridFilterQuick quickFilterPlaceholder={"Search..."} />
141
+ <GridFilterQuick/>
140
142
  <GridFilterButtons<ITestRow>
141
- options={[
142
- {
143
- label: "All",
144
- },
145
- {
146
- label: "Developers",
147
- filter: (row) => row.position === "Developer",
148
- },
149
- {
150
- label: "Testers",
151
- filter: (row) => row.position === "Tester",
152
- },
153
- ]}
143
+ options={[
144
+ {
145
+ label: "All",
146
+ },
147
+ {
148
+ label: "Developers",
149
+ filter: (row) => row.position === "Developer",
150
+ },
151
+ {
152
+ label: "Testers",
153
+ filter: (row) => row.position === "Tester",
154
+ },
155
+ ]}
154
156
  />
155
157
  <GridFilterColumnsToggle/>
158
+ <GridFilterDownloadCsvButton fileName={"exportFile"}/>
156
159
  </GridFilters>
157
- <Grid selectable={true} columnDefs={columnDefs} rowData={rowData} />
160
+ <Grid selectable={true} columnDefs={columnDefs} rowData={rowData}/>
158
161
  </GridWrapper>
159
162
  </GridContextProvider>
160
163
  </GridUpdatingContextProvider>
@@ -162,6 +165,14 @@ const GridDemo = () => {
162
165
  };
163
166
  ```
164
167
 
168
+ ## CSV Download
169
+ CSV download relies on column valueFormatters vs ag-grid's default valueGetter implementation.
170
+ If you use a customRenderer for a column be sure to include a valueFormatter.
171
+ To disable this behaviour pass undefined to processCellCallback.
172
+ ```<GridFilterDownloadCsvButton processCellCallback={undefined}/>```
173
+
174
+ To exclude a column from CSV download add ```export: false``` to the GridCell definition.
175
+
165
176
  ## Writing tests
166
177
 
167
178
  The following testing calls can be imported from step-ag-grid:
package/dist/index.css CHANGED
@@ -339,16 +339,29 @@
339
339
 
340
340
  .Grid-container-filters {
341
341
  width: 100%;
342
- margin-bottom: 16px;
343
342
  flex: 0;
344
343
  display: flex;
345
344
  align-items: center;
346
345
  flex-direction: row;
347
- grid-column-gap: 1em;
348
- padding: 0.25rem 0;
346
+ padding: 16px;
347
+ background-color: #f9f9f9;
348
+ border-bottom: 1px solid #beb9b4;
349
+ border-top: 2px solid #eaeaea;
349
350
  }
350
351
 
351
- .Grid-quickFilterBox {
352
+ .Grid-container-filters button {
353
+ margin-left: 0 !important;
354
+ }
355
+
356
+ .GridFilterQuick-container:not(:last-child), .GridFilter-container:not(:last-child) {
357
+ margin-right: 8px;
358
+ }
359
+
360
+ .GridFilterQuick-container {
361
+ flex: 1;
362
+ }
363
+
364
+ .GridFilterQuick-input {
352
365
  width: 100%;
353
366
  height: 40px;
354
367
  border: 0.06rem solid #beb9b4;
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ import { CsvExportParams } from "ag-grid-community/dist/lib/interfaces/exportParams";
3
+ export declare const GridFilterDownloadCsvButton: (csvExportParams: CsvExportParams) => JSX.Element;
@@ -3,4 +3,5 @@ export * from "./GridFilterColumnsToggle";
3
3
  export * from "./GridFilterButtons";
4
4
  export * from "./GridFilterQuick";
5
5
  export * from "./GridFilters";
6
+ export * from "./GridFilterDownloadCsvButton";
6
7
  export * from "./useGridFilter";
@@ -25,6 +25,7 @@ export interface GenericCellColDef<RowType extends GridBaseRow> extends ColDefT<
25
25
  valueFormatter?: string | ((params: RowValueFormatterParams<RowType>) => string);
26
26
  filterValueGetter?: string | ((params: RowValueGetterParams<RowType>) => string);
27
27
  editable?: boolean | ((params: RowEditableCallbackParams<RowType>) => boolean);
28
+ exportable?: boolean;
28
29
  }
29
30
  export interface GenericCellRendererParams<RowType extends GridBaseRow> {
30
31
  singleClickEdit?: boolean;
@@ -1,5 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import { ColumnApi, GridApi, RowNode } from "ag-grid-community";
3
+ import { CsvExportParams } from "ag-grid-community/dist/lib/interfaces/exportParams";
3
4
  import { ColDefT, GridBaseRow } from "../components";
4
5
  export type GridFilterExternal<RowType extends GridBaseRow> = (data: RowType, rowNode: RowNode) => boolean;
5
6
  export interface GridContextType<RowType extends GridBaseRow> {
@@ -38,6 +39,7 @@ export interface GridContextType<RowType extends GridBaseRow> {
38
39
  getColumns: () => ColDefT<RowType>[];
39
40
  invisibleColumnIds: string[];
40
41
  setInvisibleColumnIds: (colIds: string[]) => void;
42
+ downloadCsv: (csvExportParams?: CsvExportParams) => void;
41
43
  }
42
44
  export declare const GridContext: import("react").Context<GridContextType<any>>;
43
45
  export declare const useGridContext: <RowType extends GridBaseRow>() => GridContextType<RowType>;
@@ -1,3 +1,4 @@
1
+ import { ProcessCellForExportParams } from "ag-grid-community/dist/lib/interfaces/exportParams";
1
2
  import { ReactElement, ReactNode } from "react";
2
3
  import { GridBaseRow } from "../components";
3
4
  interface GridContextProps {
@@ -9,4 +10,10 @@ interface GridContextProps {
9
10
  * Also, make sure the provider is created in a separate component, otherwise it won't be found.
10
11
  */
11
12
  export declare const GridContextProvider: <RowType extends GridBaseRow>(props: GridContextProps) => ReactElement;
13
+ /**
14
+ * Aggrid defaults to using getters and ignores formatters.
15
+ * step-ag-grid by default has a valueFormatter for every column that defaults to the getter if no valueFormatter
16
+ * This function uses valueFormatter by default
17
+ */
18
+ export declare const downloadCsvUseValueFormattersProcessCellCallback: (params: ProcessCellForExportParams) => string;
12
19
  export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { LuiMiniSpinner, LuiIcon, LuiButton, LuiCheckboxInput, LuiButtonGroup } from '@linzjs/lui';
3
3
  import { AgGridReact } from 'ag-grid-react';
4
- import { negate, isEmpty, xorBy, last, difference, sortBy, findIndex, debounce, partition, omit, pick, groupBy, fromPairs, toPairs, delay, defer as defer$1, compact, remove, castArray, flatten, isEqual } from 'lodash-es';
4
+ import { negate, isEmpty, xorBy, last, difference, sortBy, findIndex, debounce, delay, partition, omit, pick, groupBy, fromPairs, toPairs, defer as defer$1, compact, remove, castArray, flatten, isEqual } from 'lodash-es';
5
5
  import * as React from 'react';
6
6
  import { createContext, useContext, useRef, useCallback, useState, useEffect, useMemo, forwardRef, useLayoutEffect, memo, useReducer, cloneElement, useImperativeHandle, Fragment as Fragment$1 } from 'react';
7
7
  import ReactDOM, { unstable_batchedUpdates, flushSync, createPortal } from 'react-dom';
@@ -244,6 +244,9 @@ var GridContext = createContext({
244
244
  doesExternalFilterPass: function () {
245
245
  console.error("no context provider for doesExternalFilterPass");
246
246
  return true;
247
+ },
248
+ downloadCsv: function () {
249
+ console.error("no context provider for downloadCsv");
247
250
  }
248
251
  });
249
252
  var useGridContext = function () { return useContext(GridContext); };
@@ -619,6 +622,9 @@ var Grid = function (_a) {
619
622
  colId: "selection",
620
623
  editable: false,
621
624
  minWidth: 42,
625
+ headerComponentParams: {
626
+ exportable: false
627
+ },
622
628
  maxWidth: 42,
623
629
  suppressSizeToFit: true,
624
630
  checkboxSelection: true,
@@ -713,7 +719,7 @@ var Grid = function (_a) {
713
719
  sizeColumnsToFit();
714
720
  }
715
721
  }, [columnDefs === null || columnDefs === void 0 ? void 0 : columnDefs.length, sizeColumnsToFit]);
716
- return (jsx("div", __assign({ "data-testid": dataTestId, className: clsx("Grid-container", theme, staleGrid && "Grid-sortIsStale", gridReady && params.rowData && "Grid-ready") }, { children: jsx("div", __assign({ style: { flex: 1 } }, { children: jsx(AgGridReact, { animateRows: params.animateRows, rowClassRules: params.rowClassRules, getRowId: function (params) { return "".concat(params.data.id); }, suppressRowClickSelection: true, rowSelection: rowSelection, suppressBrowserResizeObserver: true, colResizeDefault: "shift", onFirstDataRendered: (_c = params.onFirstDataRendered) !== null && _c !== void 0 ? _c : sizeColumnsToFit, onGridSizeChanged: (_d = params.onGridSizeChanged) !== null && _d !== void 0 ? _d : sizeColumnsToFit, suppressColumnVirtualisation: suppressColumnVirtualization, suppressClickEdit: true, onCellKeyPress: onCellKeyPress, onCellClicked: onCellClicked, onCellDoubleClicked: onCellDoubleClick, onCellEditingStarted: refreshSelectedRows, domLayout: params.domLayout, columnDefs: columnDefs, rowData: params.rowData, noRowsOverlayComponent: GridNoRowsOverlay, noRowsOverlayComponentParams: { rowData: params.rowData, noRowsOverlayText: params.noRowsOverlayText }, onModelUpdated: onModelUpdated, onGridReady: onGridReady, onSortChanged: ensureSelectedRowIsVisible, postSortRows: (_e = params.postSortRows) !== null && _e !== void 0 ? _e : postSortRows, onSelectionChanged: synchroniseExternalStateToGridSelection, onColumnMoved: params.onColumnMoved, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, isExternalFilterPresent: isExternalFilterPresent, doesExternalFilterPass: doesExternalFilterPass }) })) })));
722
+ return (jsx("div", __assign({ "data-testid": dataTestId, className: clsx("Grid-container", theme, staleGrid && "Grid-sortIsStale", gridReady && params.rowData && "Grid-ready") }, { children: jsx("div", __assign({ style: { flex: 1 } }, { children: jsx(AgGridReact, { animateRows: params.animateRows, rowClassRules: params.rowClassRules, getRowId: function (params) { return "".concat(params.data.id); }, suppressRowClickSelection: true, rowSelection: rowSelection, suppressBrowserResizeObserver: true, colResizeDefault: "shift", onFirstDataRendered: (_c = params.onFirstDataRendered) !== null && _c !== void 0 ? _c : sizeColumnsToFit, onGridSizeChanged: (_d = params.onGridSizeChanged) !== null && _d !== void 0 ? _d : sizeColumnsToFit, suppressColumnVirtualisation: suppressColumnVirtualization, suppressClickEdit: true, onCellKeyPress: onCellKeyPress, onCellClicked: onCellClicked, onCellDoubleClicked: onCellDoubleClick, onCellEditingStarted: refreshSelectedRows, domLayout: params.domLayout, columnDefs: columnDefs, rowData: params.rowData, noRowsOverlayComponent: GridNoRowsOverlay, noRowsOverlayComponentParams: { rowData: params.rowData, noRowsOverlayText: params.noRowsOverlayText }, onModelUpdated: onModelUpdated, onGridReady: onGridReady, onSortChanged: ensureSelectedRowIsVisible, postSortRows: (_e = params.postSortRows) !== null && _e !== void 0 ? _e : postSortRows, onSelectionChanged: synchroniseExternalStateToGridSelection, onColumnMoved: params.onColumnMoved, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, isExternalFilterPresent: isExternalFilterPresent, doesExternalFilterPass: doesExternalFilterPass, maintainColumnOrder: true }) })) })));
717
723
  };
718
724
 
719
725
  var GridPopoverContext = createContext({
@@ -859,6 +865,9 @@ var GridCell = function (props, custom) {
859
865
  // This is so that e.g. bearings can be searched for by DMS or raw number.
860
866
  var valueFormatter = props.valueFormatter;
861
867
  var filterValueGetter = generateFilterGetter(props.field, props.filterValueGetter, valueFormatter);
868
+ var exportable = props.exportable;
869
+ // Can't leave this here ag-grid will complain
870
+ delete props.exportable;
862
871
  return __assign(__assign(__assign(__assign(__assign(__assign({ colId: props.field, sortable: !!((props === null || props === void 0 ? void 0 : props.field) || (props === null || props === void 0 ? void 0 : props.valueGetter)), resizable: true, editable: (_a = props.editable) !== null && _a !== void 0 ? _a : false }, ((custom === null || custom === void 0 ? void 0 : custom.editor) && {
863
872
  cellClassRules: GridCellMultiSelectClassRules,
864
873
  editable: (_b = props.editable) !== null && _b !== void 0 ? _b : true,
@@ -877,7 +886,7 @@ var GridCell = function (props, custom) {
877
886
  return "".concat(params.value);
878
887
  else
879
888
  return JSON.stringify(params.value);
880
- } }), props), { cellRenderer: GridCellRenderer, cellRendererParams: __assign({ originalCellRenderer: props.cellRenderer }, props.cellRendererParams) });
889
+ } }), props), { cellRenderer: GridCellRenderer, cellRendererParams: __assign({ originalCellRenderer: props.cellRenderer }, props.cellRendererParams), headerComponentParams: __assign({ exportable: exportable }, props.headerComponentParams) });
881
890
  };
882
891
  var GenericCellEditorComponentWrapper = function (editor) {
883
892
  var obj = { editor: editor };
@@ -913,7 +922,7 @@ var GridCellMultiEditor = function (props, cellEditorSelector) {
913
922
 
914
923
  var GridFilterHeaderIconButton = forwardRef(function columnsButton(_a, ref) {
915
924
  var icon = _a.icon, title = _a.title, onClick = _a.onClick, buttonProps = _a.buttonProps, _b = _a.disabled, disabled = _b === void 0 ? false : _b, _c = _a.size, size = _c === void 0 ? "md" : _c;
916
- return (jsx(LuiButton, __assign({}, buttonProps, { type: "button", level: "tertiary", className: "GridFilterHeaderIconButton lui-button-icon-only", ref: ref, "aria-label": title, title: title, onClick: onClick, disabled: disabled }, { children: jsx(LuiIcon, { name: icon, alt: "Menu", size: size }) })));
925
+ return (jsx(LuiButton, __assign({}, buttonProps, { type: "button", level: "tertiary", className: "lui-button-icon-only", ref: ref, "aria-label": title, title: title, onClick: onClick, disabled: disabled }, { children: jsx(LuiIcon, { name: icon, alt: "Menu", size: size }) })));
917
926
  });
918
927
 
919
928
  // Generate className following BEM methodology: http://getbem.com/naming/
@@ -2863,9 +2872,9 @@ var GridFilterQuick = function (_a) {
2863
2872
  useEffect(function () {
2864
2873
  setQuickFilter(quickFilterValue);
2865
2874
  }, [quickFilterValue, setQuickFilter]);
2866
- return (jsx("input", { "aria-label": "Search", className: "Grid-quickFilterBox", type: "text", placeholder: quickFilterPlaceholder !== null && quickFilterPlaceholder !== void 0 ? quickFilterPlaceholder : "Search...", value: quickFilterValue, onChange: function (event) {
2867
- setQuickFilterValue(event.target.value);
2868
- } }));
2875
+ return (jsx("div", __assign({ className: "GridFilterQuick-container" }, { children: jsx("input", { "aria-label": "Search", className: "GridFilterQuick-input", type: "text", placeholder: quickFilterPlaceholder !== null && quickFilterPlaceholder !== void 0 ? quickFilterPlaceholder : "Search...", value: quickFilterValue, onChange: function (event) {
2876
+ setQuickFilterValue(event.target.value);
2877
+ } }) })));
2869
2878
  };
2870
2879
 
2871
2880
  var GridFilters = function (_a) {
@@ -2873,6 +2882,80 @@ var GridFilters = function (_a) {
2873
2882
  return jsx("div", __assign({ className: "Grid-container-filters" }, { children: children }));
2874
2883
  };
2875
2884
 
2885
+ /**
2886
+ * Cancels timeouts on scope being destroyed.
2887
+ *
2888
+ * This could almost be a debounce, but debounce tracks by function reference, this tracks by hook reference.
2889
+ * This could have been implemented using debounce if the callers function was wrapped in useCallback,
2890
+ * but there's no way to enforce that, so it would lead to bugs.
2891
+ */
2892
+ var useTimeoutHook = function () {
2893
+ var timeout = useRef();
2894
+ /**
2895
+ * Clear any pending timeouts.
2896
+ */
2897
+ var clearTimeouts = function () {
2898
+ if (timeout.current) {
2899
+ var tc = timeout.current;
2900
+ timeout.current = undefined;
2901
+ clearTimeout(tc);
2902
+ }
2903
+ };
2904
+ /**
2905
+ * Call this when your action has completed.
2906
+ */
2907
+ var invoke = useCallback(function (fn, waitTimeMs) {
2908
+ clearTimeouts();
2909
+ timeout.current = setTimeout(fn, waitTimeMs);
2910
+ }, []);
2911
+ /**
2912
+ * Clear timeout on loss of scope.
2913
+ */
2914
+ useEffect(function () {
2915
+ return function () { return clearTimeouts(); };
2916
+ }, []);
2917
+ return invoke;
2918
+ };
2919
+
2920
+ /**
2921
+ * Defers state change up to a minimum time since last state change.
2922
+ */
2923
+ var useStateDeferred = function (initialValue) {
2924
+ var startTime = useRef(0);
2925
+ var timeoutHook = useTimeoutHook();
2926
+ var _a = useState(initialValue), value = _a[0], _setValue = _a[1];
2927
+ var setValue = useCallback(function (newValue) {
2928
+ startTime.current = Date.now();
2929
+ _setValue(newValue);
2930
+ }, []);
2931
+ var setValueDeferred = useCallback(function (newValue, minimumWaitTimeMs) {
2932
+ var waitTimeMs = Math.max(minimumWaitTimeMs - (Date.now() - startTime.current), 0);
2933
+ timeoutHook(function () { return _setValue(newValue); }, waitTimeMs);
2934
+ }, [timeoutHook]);
2935
+ return [value, setValue, setValueDeferred];
2936
+ };
2937
+
2938
+ var GridFilterDownloadCsvButton = function (csvExportParams) {
2939
+ var _a;
2940
+ var downloadCsv = useContext(GridContext).downloadCsv;
2941
+ var _b = useStateDeferred(false), downloading = _b[0], setDownloading = _b[1], setDownloadingDeferred = _b[2];
2942
+ var handleDownloadClick = function () {
2943
+ setDownloading(true);
2944
+ // Defer the download such that the component disabled state can update
2945
+ delay(function () {
2946
+ downloadCsv(csvExportParams);
2947
+ // Defer re-enablement as the browser takes time to process the download
2948
+ setDownloadingDeferred(false, 4000);
2949
+ }, 100);
2950
+ };
2951
+ return downloading ? (jsx(LuiMiniSpinner, { size: 22, divProps: (_a = {
2952
+ role: "status"
2953
+ },
2954
+ _a["aria-label"] = "Downloading...",
2955
+ _a.style = { width: 42, display: "flex", justifyContent: "center" },
2956
+ _a) })) : (jsx(GridFilterHeaderIconButton, { icon: "ic_save_download", title: "Download CSV", onClick: handleDownloadClick, disabled: downloading }));
2957
+ };
2958
+
2876
2959
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
2877
2960
 
2878
2961
  function getDefaultExportFromCjs (x) {
@@ -4255,7 +4338,7 @@ var GridRenderPopoutMenuCell = function (props) {
4255
4338
  * Popout burger menu
4256
4339
  */
4257
4340
  var GridPopoverMenu = function (colDef, custom) {
4258
- return GridCell(__assign(__assign({ minWidth: 48, maxWidth: 48, width: 40, editable: colDef.editable != null ? colDef.editable : true, cellStyle: { justifyContent: "center" }, cellRenderer: GridRenderPopoutMenuCell }, colDef), { cellRendererParams: {
4341
+ return GridCell(__assign(__assign({ minWidth: 48, maxWidth: 48, width: 40, editable: colDef.editable != null ? colDef.editable : true, exportable: false, cellStyle: { justifyContent: "center" }, cellRenderer: GridRenderPopoutMenuCell }, colDef), { cellRendererParams: {
4259
4342
  // Menus open on single click, this parameter is picked up in Grid.tsx
4260
4343
  singleClickEdit: true
4261
4344
  } }), __assign({ editor: GridFormPopoverMenu }, custom));
@@ -4652,6 +4735,11 @@ var GridContextProvider = function (props) {
4652
4735
  gridApi.redrawRows(rowNodes ? { rowNodes: rowNodes } : undefined);
4653
4736
  });
4654
4737
  }, [gridApiOp]);
4738
+ // waitForExternallySelectedItemsToBeInSync can't use the state as it won't be updated during function execution
4739
+ var externallySelectedItemsAreInSyncRef = useRef(false);
4740
+ useEffect(function () {
4741
+ externallySelectedItemsAreInSyncRef.current = externallySelectedItemsAreInSync;
4742
+ }, [externallySelectedItemsAreInSync]);
4655
4743
  var waitForExternallySelectedItemsToBeInSync = useCallback(function () { return __awaiter(void 0, void 0, void 0, function () {
4656
4744
  var i;
4657
4745
  return __generator(this, function (_a) {
@@ -4660,7 +4748,7 @@ var GridContextProvider = function (props) {
4660
4748
  i = 0;
4661
4749
  _a.label = 1;
4662
4750
  case 1:
4663
- if (!(i < 5000 / 200 && !externallySelectedItemsAreInSync)) return [3 /*break*/, 4];
4751
+ if (!(i < 5000 / 200 && !externallySelectedItemsAreInSyncRef.current)) return [3 /*break*/, 4];
4664
4752
  return [4 /*yield*/, wait$2(200)];
4665
4753
  case 2:
4666
4754
  _a.sent();
@@ -4671,7 +4759,7 @@ var GridContextProvider = function (props) {
4671
4759
  case 4: return [2 /*return*/];
4672
4760
  }
4673
4761
  });
4674
- }); }, [externallySelectedItemsAreInSync]);
4762
+ }); }, []);
4675
4763
  var onFilterChanged = useMemo(function () {
4676
4764
  return debounce(function () {
4677
4765
  gridApi && gridApi.onFilterChanged();
@@ -4700,6 +4788,15 @@ var GridContextProvider = function (props) {
4700
4788
  columnApi.setColumnsVisible(invisibleColumnIds, false);
4701
4789
  }
4702
4790
  }, [invisibleColumnIds, columnApi, getColumns]);
4791
+ /**
4792
+ * Download visible columns as a CSV
4793
+ */
4794
+ var downloadCsv = useCallback(function (csvExportParams) {
4795
+ if (!gridApi || !columnApi)
4796
+ return;
4797
+ var columnKeys = columnApi === null || columnApi === void 0 ? void 0 : columnApi.getColumnState().filter(function (cs) { var _a, _b; return !cs.hide && ((_b = (_a = gridApi.getColumnDef(cs.colId)) === null || _a === void 0 ? void 0 : _a.headerComponentParams) === null || _b === void 0 ? void 0 : _b.exportable) !== false; }).map(function (cs) { return cs.colId; });
4798
+ gridApi.exportDataAsCsv(__assign({ columnKeys: columnKeys, processCellCallback: downloadCsvUseValueFormattersProcessCellCallback }, csvExportParams));
4799
+ }, [columnApi, gridApi]);
4703
4800
  return (jsx(GridContext.Provider, __assign({ value: {
4704
4801
  getColumns: getColumns,
4705
4802
  invisibleColumnIds: invisibleColumnIds,
@@ -4732,8 +4829,52 @@ var GridContextProvider = function (props) {
4732
4829
  addExternalFilter: addExternalFilter,
4733
4830
  removeExternalFilter: removeExternalFilter,
4734
4831
  isExternalFilterPresent: isExternalFilterPresent,
4735
- doesExternalFilterPass: doesExternalFilterPass
4832
+ doesExternalFilterPass: doesExternalFilterPass,
4833
+ downloadCsv: downloadCsv
4736
4834
  } }, { children: props.children })));
4835
+ };
4836
+ /**
4837
+ * Aggrid defaults to using getters and ignores formatters.
4838
+ * step-ag-grid by default has a valueFormatter for every column that defaults to the getter if no valueFormatter
4839
+ * This function uses valueFormatter by default
4840
+ */
4841
+ var downloadCsvUseValueFormattersProcessCellCallback = function (params) {
4842
+ var _a, _b;
4843
+ var encodeToString = function (value) {
4844
+ // Convert nullish values to blank
4845
+ if (value === "-" || value === "–" || value == null) {
4846
+ return "";
4847
+ }
4848
+ if (typeof value === "string") {
4849
+ return value;
4850
+ }
4851
+ return JSON.stringify(value);
4852
+ };
4853
+ // Try to use valueFormatter
4854
+ var colDef = (_a = params === null || params === void 0 ? void 0 : params.column) === null || _a === void 0 ? void 0 : _a.getColDef();
4855
+ if (!colDef)
4856
+ return encodeToString(params.value);
4857
+ // All columns in step-ag-grid have a default valueFormatter
4858
+ // If you have custom a renderer you need to define your own valueFormatter to produce the text value
4859
+ var valueFormatter = colDef.valueFormatter;
4860
+ // If no valueFormatter then value _must_ be a string
4861
+ if (valueFormatter == null) {
4862
+ if (params.value != null && typeof params.value !== "string") {
4863
+ console.error("downloadCsv: valueFormatter missing and getValue is not a string, colId: ".concat(colDef.colId));
4864
+ }
4865
+ return encodeToString(params.value);
4866
+ }
4867
+ // We don't have access to registered functions, so we can't call them
4868
+ if (typeof valueFormatter !== "function") {
4869
+ console.error("downloadCsv: String type (registered) value formatters are unsupported in downloadCsv, colId: ".concat(colDef.colId));
4870
+ return encodeToString(params.value);
4871
+ }
4872
+ var result = valueFormatter(__assign(__assign({}, params), { data: (_b = params.node) === null || _b === void 0 ? void 0 : _b.data, colDef: colDef }));
4873
+ if (params.value != null && typeof result !== "string") {
4874
+ console.error("downloadCsv: valueFormatter is returning non string values, colDef:\", colId: ".concat(colDef.colId));
4875
+ }
4876
+ // We add an extra encodeToString here just in case valueFormatter is returning non string values
4877
+ return encodeToString(result);
4737
4878
  };
4738
4879
 
4739
4880
  var GridUpdatingContextProvider = function (props) {
@@ -4794,59 +4935,6 @@ var usePrevious = function (value) {
4794
4935
  return ref.current;
4795
4936
  };
4796
4937
 
4797
- /**
4798
- * Cancels timeouts on scope being destroyed.
4799
- *
4800
- * This could almost be a debounce, but debounce tracks by function reference, this tracks by hook reference.
4801
- * This could have been implemented using debounce if the callers function was wrapped in useCallback,
4802
- * but there's no way to enforce that, so it would lead to bugs.
4803
- */
4804
- var useTimeoutHook = function () {
4805
- var timeout = useRef();
4806
- /**
4807
- * Clear any pending timeouts.
4808
- */
4809
- var clearTimeouts = function () {
4810
- if (timeout.current) {
4811
- var tc = timeout.current;
4812
- timeout.current = undefined;
4813
- clearTimeout(tc);
4814
- }
4815
- };
4816
- /**
4817
- * Call this when your action has completed.
4818
- */
4819
- var invoke = useCallback(function (fn, waitTimeMs) {
4820
- clearTimeouts();
4821
- timeout.current = setTimeout(fn, waitTimeMs);
4822
- }, []);
4823
- /**
4824
- * Clear timeout on loss of scope.
4825
- */
4826
- useEffect(function () {
4827
- return function () { return clearTimeouts(); };
4828
- }, []);
4829
- return invoke;
4830
- };
4831
-
4832
- /**
4833
- * Defers state change up to a minimum time since last state change.
4834
- */
4835
- var useStateDeferred = function (initialValue) {
4836
- var startTime = useRef(0);
4837
- var timeoutHook = useTimeoutHook();
4838
- var _a = useState(initialValue), value = _a[0], _setValue = _a[1];
4839
- var setValue = useCallback(function (newValue) {
4840
- startTime.current = Date.now();
4841
- _setValue(newValue);
4842
- }, []);
4843
- var setValueDeferred = useCallback(function (newValue, minimumWaitTimeMs) {
4844
- var waitTimeMs = Math.max(minimumWaitTimeMs - (Date.now() - startTime.current), 0);
4845
- timeoutHook(function () { return _setValue(newValue); }, waitTimeMs);
4846
- }, [timeoutHook]);
4847
- return [value, setValue, setValueDeferred];
4848
- };
4849
-
4850
4938
  // Kept this less than one second, so I don't have issues with waitFor as it defaults to 1s
4851
4939
  var minimumInProgressTimeMs = 950;
4852
4940
  var ActionButton = function (_a) {
@@ -25000,5 +25088,5 @@ var clickActionButton = function (text, container) { return __awaiter(void 0, vo
25000
25088
  });
25001
25089
  }); };
25002
25090
 
25003
- export { ActionButton, ComponentLoadingWrapper, ControlledMenu, Editor, FocusableItem, FormError, GenericCellEditorComponentWrapper, Grid, GridCell, GridCellMultiEditor, GridCellMultiSelectClassRules, GridCellRenderer, GridContext, GridContextProvider, GridFilterButtons, GridFilterColumnsToggle, GridFilterHeaderIconButton, GridFilterQuick, GridFilters, GridFormDropDown, GridFormEditBearing, GridFormMessage, GridFormMultiSelect, GridFormPopoverMenu, GridFormSubComponentTextArea, GridFormSubComponentTextInput, GridFormTextArea, GridFormTextInput, GridHeaderSelect, GridIcon, GridLoadableCell, GridNoRowsOverlay, GridPopoutEditMultiSelect, GridPopoverContext, GridPopoverContextProvider, GridPopoverEditBearing, GridPopoverEditBearingCorrection, GridPopoverEditBearingCorrectionEditorParams, GridPopoverEditBearingEditorParams, GridPopoverEditDropDown, GridPopoverMenu, GridPopoverMessage, GridPopoverTextArea, GridPopoverTextInput, GridRenderPopoutMenuCell, GridSubComponentContext, GridUpdatingContext, GridUpdatingContextProvider, GridWrapper, Menu, MenuButton, MenuDivider, MenuGroup, MenuHeader, MenuHeaderItem, MenuHeaderString, MenuItem, MenuRadioGroup, MenuSeparator, MenuSeparatorString, PopoutMenuSeparator, SubMenu, TextAreaInput, TextInputFormatted, bearingCorrectionRangeValidator, bearingCorrectionValueFormatter, bearingNumberParser, bearingRangeValidator, bearingStringValidator, bearingValueFormatter, clickActionButton, clickMenuOption, clickMultiSelectOption, closeMenu, closePopover, convertDDToDMS, countRows, deselectRow, editCell, findActionButton, findCell, findCellContains, findMenuOption, findMultiSelectOption, findOpenPopover, findParentWithClass, findRow, fnOrVar, generateFilterGetter, getMultiSelectOptions, hasParentClass, isCellReadOnly, isFloat, isNotEmpty, openAndClickMenuOption, openAndFindMenuOption, queryMenuOption, queryRow, selectCell, selectRow, stringByteLengthIsInvalid, suppressCellKeyboardEvents, typeInputByLabel, typeInputByPlaceholder, typeOnlyInput, typeOtherInput, typeOtherTextArea, useDeferredPromise, useGenerateOrDefaultId, useGridContext, useGridFilter, useGridPopoverContext, useGridPopoverHook, useMenuState, usePostSortRowsHook, validateMenuOptions, wait$2 as wait };
25091
+ export { ActionButton, ComponentLoadingWrapper, ControlledMenu, Editor, FocusableItem, FormError, GenericCellEditorComponentWrapper, Grid, GridCell, GridCellMultiEditor, GridCellMultiSelectClassRules, GridCellRenderer, GridContext, GridContextProvider, GridFilterButtons, GridFilterColumnsToggle, GridFilterDownloadCsvButton, GridFilterHeaderIconButton, GridFilterQuick, GridFilters, GridFormDropDown, GridFormEditBearing, GridFormMessage, GridFormMultiSelect, GridFormPopoverMenu, GridFormSubComponentTextArea, GridFormSubComponentTextInput, GridFormTextArea, GridFormTextInput, GridHeaderSelect, GridIcon, GridLoadableCell, GridNoRowsOverlay, GridPopoutEditMultiSelect, GridPopoverContext, GridPopoverContextProvider, GridPopoverEditBearing, GridPopoverEditBearingCorrection, GridPopoverEditBearingCorrectionEditorParams, GridPopoverEditBearingEditorParams, GridPopoverEditDropDown, GridPopoverMenu, GridPopoverMessage, GridPopoverTextArea, GridPopoverTextInput, GridRenderPopoutMenuCell, GridSubComponentContext, GridUpdatingContext, GridUpdatingContextProvider, GridWrapper, Menu, MenuButton, MenuDivider, MenuGroup, MenuHeader, MenuHeaderItem, MenuHeaderString, MenuItem, MenuRadioGroup, MenuSeparator, MenuSeparatorString, PopoutMenuSeparator, SubMenu, TextAreaInput, TextInputFormatted, bearingCorrectionRangeValidator, bearingCorrectionValueFormatter, bearingNumberParser, bearingRangeValidator, bearingStringValidator, bearingValueFormatter, clickActionButton, clickMenuOption, clickMultiSelectOption, closeMenu, closePopover, convertDDToDMS, countRows, deselectRow, downloadCsvUseValueFormattersProcessCellCallback, editCell, findActionButton, findCell, findCellContains, findMenuOption, findMultiSelectOption, findOpenPopover, findParentWithClass, findRow, fnOrVar, generateFilterGetter, getMultiSelectOptions, hasParentClass, isCellReadOnly, isFloat, isNotEmpty, openAndClickMenuOption, openAndFindMenuOption, queryMenuOption, queryRow, selectCell, selectRow, stringByteLengthIsInvalid, suppressCellKeyboardEvents, typeInputByLabel, typeInputByPlaceholder, typeOnlyInput, typeOtherInput, typeOtherTextArea, useDeferredPromise, useGenerateOrDefaultId, useGridContext, useGridFilter, useGridPopoverContext, useGridPopoverHook, useMenuState, usePostSortRowsHook, validateMenuOptions, wait$2 as wait };
25004
25092
  //# sourceMappingURL=step-ag-grid.esm.js.map