Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.css
registerServiceWorker.js
registerServiceWorker.js
node_modules/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ Links "DE#nnn" prior to version 2.0 point to the Dash Enterprise closed-source D

### Added
- [#436](https://github.com/plotly/dash-ag-grid/pull/436) Enabled Filter Handlers to simplify custom filter components by splitting the filter logic out from the UI component.
- [#440](https://github.com/plotly/dash-ag-grid/pull/440)
- added `columnTypes` to prop categories for parsing functions

### Changed
- [#440](https://github.com/plotly/dash-ag-grid/pull/440)
- Markdown update for npm
- `linkTarget` now works even if `dangerously_allow_code` is `true` and no `target` is passed in the link.
- `_self` will auto apply as a `linkTarget`.
- `_blank` will auto apply `rel='noreferrer noopener 'nofollow'` for security reasons, but will open in a new tab.
- version bump to v`35.2.0` for the grid

## [34.0.0rc0] - 2026-01-21
### Fixed
Expand Down
14,330 changes: 6,188 additions & 8,142 deletions package-lock.json

Large diffs are not rendered by default.

61 changes: 32 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,62 +18,65 @@
"build:js": "webpack --mode production",
"build:backends": "dash-generate-components ./src/lib/components dash_ag_grid -p package-info.json --r-prefix '' --jl-prefix ''",
"build": "run-s pre-flight-version && run-s prepublishOnly build:js build:backends",
"postbuild": "es-check es2017 dash_ag_grid/*.js",
"postbuild": "es-check es2018 dash_ag_grid/*.js",
"private::format.eslint": "eslint --quiet --fix src",
"private::format.prettier": "prettier --write src --ignore-path=.prettierignore",
"format": "run-s private::format.*",
"private::lint.eslint": "eslint src",
"private::lint.prettier": "prettier src --list-different --ignore-path=.prettierignore",
"lint": "run-s private::lint.*",
"dist": "npm run build && run-s pre-flight-dag-version && python setup.py sdist bdist_wheel"
"dist": "npm run build && run-s pre-flight-dag-version && python setup.py sdist bdist_wheel",
"update-packages": "ncu -u && npm install"
},
"author": "Plotly <chris@plot.ly>",
"license": "MIT",
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.7",
"@mui/material": "^5.15.7",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.9",
"@mui/material": "^7.3.9",
"ag-grid-community": "34.3.1",
"ag-grid-enterprise": "34.3.1",
"ag-grid-react": "34.3.1",
"d3-format": "^3.1.0",
"d3-format": "^3.1.2",
"d3-time": "^3.1.0",
"d3-time-format": "^4.1.0",
"esprima": "^4.0.1",
"ramda": "^0.29.1",
"react-markdown": "^8.0.7",
"ramda": "^0.32.0",
"react-markdown": "^10.1.0",
"rehype-external-links": "^3.0.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^3.0.1",
"remark-gfm": "^4.0.1",
"static-eval": "^2.1.1"
},
"devDependencies": {
"@babel/cli": "^7.23.9",
"@babel/core": "^7.23.9",
"@babel/eslint-parser": "^7.23.10",
"@babel/cli": "^7.28.6",
"@babel/core": "^7.29.0",
"@babel/eslint-parser": "^7.28.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"@babel/preset-env": "^7.29.0",
"@babel/preset-react": "^7.28.5",
"@plotly/webpack-dash-dynamic-import": "^1.3.0",
"babel-loader": "^9.1.3",
"css-loader": "^6.10.0",
"babel-loader": "^10.1.1",
"css-loader": "^7.1.4",
"es-check": "^7.1.1",
"esbuild-loader": "^4.1.0",
"esbuild-loader": "^4.4.2",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react": "^7.33.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react": "^7.37.5",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.4",
"prettier": "^3.8.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react": "^18.3.1",
"react-docgen": "^5.4.3",
"react-dom": "^18.2.0",
"rimraf": "^5.0.5",
"style-loader": "^3.3.4",
"styled-jsx": "^5.1.2",
"webpack": "^5.90.1",
"webpack-cli": "^5.1.4"
"react-dom": "^18.3.1",
"npm-check-updates": "^19.6.3",
"rimraf": "^6.1.3",
"style-loader": "^4.0.0",
"styled-jsx": "^5.1.7",
"webpack": "^5.105.4",
"webpack-cli": "^6.0.1"
},
"files": [
"/dash_ag_grid/*{.js,.map}",
Expand Down
58 changes: 36 additions & 22 deletions src/lib/renderers/markdownRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,53 @@ import PropTypes from 'prop-types';

import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
import rehypeExternalLinks from 'rehype-external-links';

import ReactMarkdown from 'react-markdown';

export default function MarkdownRenderer(props) {
const {colDef, target, value, dangerously_allow_code} = props;
const {colDef, value, dangerously_allow_code} = props;
// Markdown renderer with HTML rendering enabled.
// rehypeRaw allows HTML rendering.
// Convert <p> tags to simple <divs> using the components prop.
const rehypePlugins = dangerously_allow_code ? [rehypeRaw] : [];

let linkTarget;
if (!dangerously_allow_code) {
linkTarget = colDef.linkTarget || '_self';
}
const linkTarget = colDef.linkTarget || '_self';

rehypePlugins.push([
rehypeExternalLinks,
{
target: dangerously_allow_code ? linkTarget : null,
rel: ['noopener', 'noreferrer', 'nofollow'],
},
]);

return (
<ReactMarkdown
linkTarget={linkTarget}
remarkPlugins={[[remarkGfm, {singleTilde: false}]]}
components={{
p: 'div',
a: ({node: _, children, ...props}) => {
const linkProps = props;
if (target === '_blank') {
linkProps.rel = 'noopener noreferrer';
}
return <a {...linkProps}>{children}</a>;
},
}}
className="agGrid-Markdown"
rehypePlugins={rehypePlugins}
children={value ? String(value) : null}
/>
<div className="agGrid-Markdown">
<ReactMarkdown
remarkPlugins={[[remarkGfm, {singleTilde: false}]]}
components={{
p: 'div',
a({node: _, children, target, ...props}) {
const linkProps = props;
const subLinkTarget = dangerously_allow_code
? target || linkTarget
: linkTarget;
// Use the correct target for links
if (subLinkTarget === '_blank') {
linkProps.rel = 'noopener noreferrer nofollow';
}
return (
<a {...{target: subLinkTarget, ...linkProps}}>
{children}
</a>
);
},
}}
rehypePlugins={rehypePlugins}
children={value ? String(value) : null}
/>
</div>
);
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/utils/propCategories.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ export const COLUMN_ARRAY_NESTED_FUNCTIONS = {
export const OBJ_MAYBE_FUNCTION_OR_MAP_MAYBE_FUNCTIONS = {
dataTypeDefinitions: 1,
aggFuncs: 1,
columnTypes: 1,
};

/**
Expand Down
67 changes: 67 additions & 0 deletions tests/test_cell_data_type_override.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,70 @@ def test_cd001_cell_data_types_override(enforced_locale, dash_duo):
date_input_element.send_keys("01172024" + Keys.ENTER)

grid.wait_for_cell_text(0, 1, "17/01/2024")


def test_cd002_column_types_formatting(dash_duo):
app = Dash(__name__)

rowData = [
{"col1": 0.12345, "col2": 0.98765},
{"col1": 0.5, "col2": 0.25},
]

columnDefs = [
{
"field": "col1",
"type": "rightAligned",
"valueFormatter": {"function": "d3.format('.3f')(params.value)"},
},
{
"field": "col2",
"type": "float",
},
]

dashGridOptions = {
"columnTypes": {
"float": {
"cellClass": "ag-right-aligned-cell",
"headerClass": "ag-right-aligned-header",
"valueFormatter": {
"function": "d3.format('.3f')(params.value)"
},
}
}
}

app.layout = html.Div(
[
dag.AgGrid(
id="grid-column-types",
columnDefs=columnDefs,
rowData=rowData,
defaultColDef={"editable": True},
dashGridOptions=dashGridOptions,
)
]
)

dash_duo.start_server(app)

grid = utils.Grid(dash_duo, "grid-column-types")

action = utils.ActionChains(dash_duo.driver)

# ---- Test col1 formatter ----
# edit valye
action.double_click(grid.get_cell(0, 0)).perform()
input_el = dash_duo.find_element("#grid-column-types .ag-input-field-input")
input_el.send_keys("0.1" + Keys.ENTER)

# expect formatted to 3 decimals
grid.wait_for_cell_text(0, 0, "0.100")

# ---- Test col2 formatter via column type ----
action.double_click(grid.get_cell(0, 1)).perform()
input_el = dash_duo.find_element("#grid-column-types .ag-input-field-input")
input_el.send_keys("0.2" + Keys.ENTER)

grid.wait_for_cell_text(0, 1, "0.200")
9 changes: 4 additions & 5 deletions tests/test_column_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,19 +567,18 @@ def toggle_column_visibility(selected_columns):

# Hide column 'b'
dropdown = dash_duo.find_element("#select-columns")
option_b = dash_duo.find_element('span.Select-value-icon:nth-child(1)')
dropdown.click()
option_b = dash_duo.find_element('label:nth-child(2) > span.dash-options-list-option-wrapper > input')
option_b.click()
time.sleep(1)

# Only column 'a' should be visible
grid_headers = dash_duo.find_elements("div.ag-header-cell-label")
header_texts = [h.text for h in grid_headers]
assert "a" not in header_texts
assert "b" in header_texts
assert "a" in header_texts
assert "b" not in header_texts

# Show both columns again
dropdown.click()
option_b = dash_duo.find_element('.Select-menu')
option_b.click()
time.sleep(1)
grid_headers = dash_duo.find_elements("div.ag-header-cell-label")
Expand Down
6 changes: 3 additions & 3 deletions tests/test_markdown_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,20 @@ def test_mc001_markdown_components(dash_duo):

assert (
safe_grid.get_cell(2, 2).get_attribute("innerHTML")
== '<div class="agGrid-Markdown"><div><a href="#" target="_blank">Example</a></div></div>'
== '<div class="agGrid-Markdown"><div><a target="_blank" href="#" rel="noopener noreferrer nofollow">Example</a></div></div>'
)
safe_grid.wait_for_cell_text(
1, 2, '<a href="#" target="_blank">Link to new tab</a>'
)

assert (
dangerous_grid.get_cell(2, 2).get_attribute("innerHTML")
== '<div class="agGrid-Markdown"><div><a href="#">Example</a></div></div>'
== '<div class="agGrid-Markdown"><div><a target="_self" href="#">Example</a></div></div>'
)
dangerous_grid.wait_for_cell_text(1, 2, "Link to new tab")
assert (
dangerous_grid.get_cell(1, 2).get_attribute("innerHTML")
== '<div class="agGrid-Markdown"><div><a href="#" target="_blank">Link to new tab</a></div></div>'
== '<div class="agGrid-Markdown"><div><a target="_blank" href="#" rel="noopener noreferrer nofollow">Link to new tab</a></div></div>'
)

assert safe2_grid.get_cell(0, 2).text == ""
Expand Down
Loading