Skip to content
Merged
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
24 changes: 23 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
name: Tests
name: CI

on:
pull_request:

jobs:
lint:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

- name: Run linter
run: pnpm lint

test:
runs-on: ubuntu-latest

Expand Down
2 changes: 2 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pnpm lint
pnpm test
8 changes: 7 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ export default defineConfig(
'@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/no-trailing-spaces': ['error']
'@stylistic/no-trailing-spaces': ['error'],
'@stylistic/max-len': ['error', {
code: 80,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true
}]
},
},
{
Expand Down
71 changes: 37 additions & 34 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
{
"name": "highcharts-grid-react",
"description": "Monorepo for Highcharts Grid Pro & Lite React libraries.",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"pretest:e2e": "pnpm build",
"test:e2e": "vitest run --config vitest.e2e.config.ts",
"test:all": "pnpm test && pnpm test:e2e",
"check": "pnpm lint && pnpm test",
"build": "pnpm -r --filter './packages/*' run build",
"lint": "pnpm -r --filter './packages/*' run lint",
"clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules",
"release:preflight": "pnpm check && pnpm build",
"release:prepare": "node scripts/release.js",
"release": "pnpm release:preflight && pnpm publish -r --access public"
},
"packageManager": "pnpm@10.27.0",
"devDependencies": {
"@eslint/js": "^9.39.1",
"@stylistic/eslint-plugin": "^5.6.1",
"@testing-library/react": "^16.3.1",
"@types/node": "^20.0.0",
"@vitest/browser": "^4.0.16",
"@vitest/browser-playwright": "^4.0.16",
"eslint": "^9.39.1",
"globals": "^17.0.0",
"playwright": "^1.57.0",
"rimraf": "^6.1.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.0",
"vitest": "^4.0.16"
}
"name": "highcharts-grid-react",
"description": "Monorepo for Highcharts Grid Pro & Lite React libraries.",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run",
"pretest": "pnpm build",
"test:watch": "vitest",
"pretest:e2e": "pnpm build",
"test:e2e": "vitest run --config vitest.e2e.config.ts",
"test:all": "pnpm test && pnpm test:e2e",
"check": "pnpm lint && pnpm test",
"build": "pnpm -r --filter './packages/*' run build",
"lint": "pnpm -r --filter './packages/*' run lint",
"clean": "pnpm -r --filter './{packages,examples}/*' run clean && rimraf node_modules",
"release:preflight": "pnpm check && pnpm build",
"release:prepare": "node scripts/release.js",
"release": "pnpm release:preflight && pnpm publish -r --access public",
"prepare": "husky"
},
"packageManager": "pnpm@10.27.0",
"devDependencies": {
"@eslint/js": "^9.39.1",
"@stylistic/eslint-plugin": "^5.6.1",
"@testing-library/react": "^16.3.1",
"@types/node": "^20.0.0",
"@vitest/browser": "^4.0.16",
"@vitest/browser-playwright": "^4.0.16",
"eslint": "^9.39.1",
"globals": "^17.0.0",
"husky": "^9.1.7",
"playwright": "^1.57.0",
"rimraf": "^6.1.2",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.0",
"vitest": "^4.0.16"
}
}
3 changes: 2 additions & 1 deletion packages/grid-lite-react/src/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export default function GridLite(props: GridProps<Options>) {
const { gridRef, children, options, ...gridProps } = props;
const childOptions = useMemo(() => getChildProps(children), [children]);
const columnKey = useMemo(() => {
const columns = childOptions.columns as Array<{ id?: string }> | undefined;
const columns = childOptions.columns as
Array<{ id?: string }> | undefined;

return columns?.map((column) => column.id).join('\0') ?? '';
}, [childOptions]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests';
import { Grid, GridOptions } from '../index';
import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests';
import { Grid, GridOptions } from '../src/index';

createGridTests<GridOptions>(
'Grid Lite',
Expand Down
3 changes: 2 additions & 1 deletion packages/grid-pro-react/src/Grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export default function GridPro(props: GridProps<Options>) {
const { gridRef, children, options, ...gridProps } = props;
const childOptions = useMemo(() => getChildProps(children), [children]);
const columnKey = useMemo(() => {
const columns = childOptions.columns as Array<{ id?: string }> | undefined;
const columns = childOptions.columns as
Array<{ id?: string }> | undefined;

return columns?.map((column) => column.id).join('\0') ?? '';
}, [childOptions]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createGridTests } from '@highcharts/grid-shared-react/src/test/createGridTests';
import { Grid, GridOptions } from '../index';
import { createGridTests } from '@highcharts/grid-shared-react/tests/createGridTests';
import { Grid, GridOptions } from '../src/index';

createGridTests<GridOptions>(
'Grid Pro',
Expand Down
6 changes: 4 additions & 2 deletions packages/grid-shared-react/src/components/BaseGridOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
*/

/**
* Metadata attached to declarative option components rendered as BaseGrid children.
* Metadata attached to declarative option components
* rendered as BaseGrid children.
*/
export interface BaseGridOptions {
type: 'Grid_Option';
Expand All @@ -25,7 +26,8 @@ export interface BaseGridOptions {
}

/**
* A React component that maps JSX props to a Grid options path via `_GridReact`.
* A React component that maps JSX props to a Grid options path
* via `_GridReact`.
*/
export interface BaseGridOptionsComponent {
_GridReact: BaseGridOptions;
Expand Down
21 changes: 15 additions & 6 deletions packages/grid-shared-react/src/hooks/useGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export interface GridInstance<TOptions> {
* directly depending on their types.
*/
export interface GridType<TOptions> {
grid(container: HTMLDivElement, options?: TOptions, async?: boolean): GridInstance<TOptions> | Promise<GridInstance<TOptions>>;
grid(
container: HTMLDivElement,
options?: TOptions,
async?: boolean
): GridInstance<TOptions> | Promise<GridInstance<TOptions>>;
}

export interface UseGridOptions<TOptions> {
Expand Down Expand Up @@ -62,7 +66,8 @@ export function useGrid<TOptions>({
return;
}

// StrictMode cleanup runs before re-mount; allow init to complete if re-mounted.
// StrictMode cleanup runs before re-mount;
// allow init to complete if re-mounted.
destroyOnInitRef.current = false;

// Prevent double initialization
Expand All @@ -73,21 +78,24 @@ export function useGrid<TOptions>({

const initGrid = async () => {
try {
// Use pending options if available (from rapid updates during init)
// Use pending options if available
// (from rapid updates during init)
const initOptions = pendingOptionsRef.current ?? options;
pendingOptionsRef.current = void 0;

const grid = await Grid.grid(container, initOptions, true);

if (destroyOnInitRef.current) {
// Component unmounted while we were initializing - destroy immediately
// Component unmounted while initializing -
// destroy immediately
grid.destroy();
return;
}

currGridRef.current = grid;

// Apply any pending options that came in while we were initializing
// Apply pending options that came in
// while we were initializing
if (pendingOptionsRef.current !== void 0) {
grid.update(pendingOptionsRef.current, true, true);
pendingOptionsRef.current = void 0;
Expand Down Expand Up @@ -122,7 +130,8 @@ export function useGrid<TOptions>({
}

if (currGridRef.current) {
// Declarative React options replace the previous snapshot (oneToOne).
// Declarative React options replace the previous
// snapshot (oneToOne).
currGridRef.current.update(options, true, true);
} else {
// Grid still initializing, queue the update
Expand Down
60 changes: 42 additions & 18 deletions packages/grid-shared-react/src/utils/getChildProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ function getOptionComponent(type: unknown): BaseGridOptionsComponent | null {
return component._GridReact ? type as BaseGridOptionsComponent : null;
}

function getChildPropsFromElement(child: ReactElement): Record<string, unknown> {
function getChildPropsFromElement(
child: ReactElement
): Record<string, unknown> {
return (child.props ?? {}) as Record<string, unknown>;
}

Expand Down Expand Up @@ -87,7 +89,8 @@ function flattenChildren(childNodes: ReactNode): ReactNode[] {
}

if (isReactElement(childNodes) && childNodes.type === Fragment) {
return flattenChildren((childNodes.props as { children?: ReactNode }).children);
const fragmentProps = childNodes.props as { children?: ReactNode };
return flattenChildren(fragmentProps.children);
}

return [childNodes];
Expand Down Expand Up @@ -115,7 +118,15 @@ function getEffectiveMeta(
}

function parseColumnElement(child: ReactElement): Record<string, unknown> {
const { children: _ignored, columnId, id: _cssId, ...props } = getChildPropsFromElement(child);
const {
children,
id,
columnId,
...props
} = getChildPropsFromElement(child);
void children;
void id;

const options = normalizeColumnOptions(props);

// columnId selects the column; Core expects the same value as `id`.
Expand Down Expand Up @@ -178,7 +189,10 @@ export function getChildProps(children: ReactNode): Record<string, unknown> {
}
}

function handleChild(child: ReactElement, parentMeta?: BaseGridOptions): void {
function handleChild(
child: ReactElement,
parentMeta?: BaseGridOptions
): void {
const component = getOptionComponent(child.type);

if (!component) {
Expand Down Expand Up @@ -206,18 +220,17 @@ export function getChildProps(children: ReactNode): Record<string, unknown> {

if (meta.gridOption === 'pagination') {
const pagination = normalizePaginationOptions(props);
pagination.position = isTopPaginationChild(child, resolvedChildren) ?
'top' :
'bottom';
pagination.position = isTopPaginationChild(
child,
resolvedChildren
) ? 'top' : 'bottom';
optionsFromChildren.pagination = pagination;
return;
}

if (meta.gridOption === 'header') {
const { header, children: _ignored } = props;

if (header !== void 0) {
optionsFromChildren.header = header;
if (props.header !== void 0) {
optionsFromChildren.header = props.header;
}
return;
}
Expand All @@ -226,7 +239,9 @@ export function getChildProps(children: ReactNode): Record<string, unknown> {
optionsFromChildren[meta.gridOption] = meta.isArrayType ? [] : {}
);
const parentIsArray = Array.isArray(optionParent);
const insertInto = parentIsArray ? {} : optionParent as Record<string, unknown>;
const insertInto = parentIsArray
? {}
: optionParent as Record<string, unknown>;

if (meta.defaultOptions) {
Object.assign(insertInto, meta.defaultOptions);
Expand All @@ -243,7 +258,10 @@ export function getChildProps(children: ReactNode): Record<string, unknown> {
}

if (parentIsArray) {
(optionsFromChildren[meta.gridOption] as unknown[]).push(insertInto);
const optionItems = optionsFromChildren[
meta.gridOption
] as unknown[];
optionItems.push(insertInto);
}
}

Expand All @@ -258,7 +276,8 @@ export function getChildProps(children: ReactNode): Record<string, unknown> {

/**
* When declarative `<Column>` components are present, only those columns
* should render unless `data.autogenerateColumns` is set explicitly on `<Data>`.
* should render unless `data.autogenerateColumns` is set
* explicitly on `<Data>`.
*/
function applyDeclarativeColumnDefaults(
optionsFromChildren: Record<string, unknown>
Expand Down Expand Up @@ -291,7 +310,11 @@ function isTopPaginationChild(

return children
.slice(0, childIndex)
.every((candidate) => getOptionComponent(candidate.type)?._GridReact.gridOption === 'pagination');
.every((candidate) => {
const gridOption = getOptionComponent(candidate.type)
?._GridReact.gridOption;
return gridOption === 'pagination';
});
}

function isOptionElement(child: ReactElement): boolean {
Expand All @@ -313,9 +336,10 @@ function resolveOptionChild(child: ReactNode): ReactElement | null {
return null;
}

const rendered = (child.type as (props: Record<string, unknown>) => ReactNode)(
getChildPropsFromElement(child)
);
const renderChild = child.type as (
props: Record<string, unknown>
) => ReactNode;
const rendered = renderChild(getChildPropsFromElement(child));

if (isReactElement(rendered) && getOptionComponent(rendered.type)) {
return rendered;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ export function mapPrefixedProps(
): Record<string, unknown> {
const result = { ...props };
const groups: Record<string, Record<string, unknown>> = {};
const prefixes = Object.keys(prefixToGroup).sort((a, b) => b.length - a.length);
const prefixes = Object.keys(prefixToGroup)
.sort((a, b) => b.length - a.length);

for (const flatKey of Object.keys(result)) {
const prefix = prefixes.find(
(candidate) => flatKey.startsWith(candidate) && flatKey.length > candidate.length
(candidate) => (
flatKey.startsWith(candidate)
&& flatKey.length > candidate.length
)
);

if (!prefix) {
Expand Down
Loading
Loading