From 6acaeb2459e92c0921f081f161da40913f884a98 Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 11:24:37 +0300 Subject: [PATCH 1/6] add(sample): added keyboard nav samples --- .../keyboard-custom-navigation/src/index.tsx | 584 +++- .../.eslintrc.js | 78 + .../ReadMe.md | 56 + .../package.json | 48 + .../public/index.html | 11 + .../sandbox.config.json | 5 + .../src/NwindData.json | 458 +++ .../src/SingersData.json | 2496 +++++++++++++++++ .../src/index.css | 7 + .../src/index.tsx | 304 ++ .../src/react-app-env.d.ts | 1 + .../tsconfig.json | 44 + .../.eslintrc.js | 78 + .../ReadMe.md | 56 + .../package.json | 48 + .../public/index.html | 11 + .../sandbox.config.json | 5 + .../src/EmployeesNestedData.ts | 75 + .../src/NwindData.json | 458 +++ .../src/index.css | 7 + .../src/index.tsx | 379 +++ .../src/react-app-env.d.ts | 1 + .../tsconfig.json | 44 + 23 files changed, 5146 insertions(+), 108 deletions(-) create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/.eslintrc.js create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/package.json create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/public/index.html create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/sandbox.config.json create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/NwindData.json create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/SingersData.json create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.css create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/react-app-env.d.ts create mode 100644 samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/tsconfig.json create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/NwindData.json create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json diff --git a/samples/grids/grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/grid/keyboard-custom-navigation/src/index.tsx index 5988213a66..92bccca83b 100644 --- a/samples/grids/grid/keyboard-custom-navigation/src/index.tsx +++ b/samples/grids/grid/keyboard-custom-navigation/src/index.tsx @@ -1,131 +1,499 @@ -import React from 'react'; + +import React, { useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; - -import { IgrPropertyEditorPanelModule } from 'igniteui-react-layouts'; -import { IgrGridModule } from 'igniteui-react-grids'; -import { IgrGrid, IgrColumn } from 'igniteui-react-grids'; -import { ComponentRenderer, PropertyEditorPanelDescriptionModule, WebGridDescriptionModule } from 'igniteui-react-core'; +import { + IgrGrid, + IgrGridModule, + IgrColumn, + IgrColumnGroup, + IgrPaginator, + IgrGridToolbar, + IgrGridMasterDetailContext, +} from 'igniteui-react-grids'; +import { IgrCheckbox } from 'igniteui-react'; +import { IgrList, IgrListItem, IgrListHeader } from 'igniteui-react'; import NwindData from './NwindData.json'; -import { IgrGridKeydownEventArgs } from 'igniteui-react-grids'; import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; -const mods: any[] = [ - IgrPropertyEditorPanelModule, - IgrGridModule +IgrGridModule.register(); + +interface MasterDetailTemplateProps { + dataContext: IgrGridMasterDetailContext; +} + +type Customer = { + ProductID: number; + ProductName: string; + SupplierID: number; + CategoryID: number; + QuantityPerUnit: string; + UnitPrice: number; + UnitsInStock: number; + UnitsOnOrder: number; + ReorderLevel: number; + Discontinued: boolean; + OrderDate: string; + Rating: number; + Locations?: any[]; + CompanysAnnualProfit?: number; +}; +enum GridSection { + THEAD = 'thead', + TBODY = 'tbody', + FOOTER = 'tfoot', +} + +enum ItemAction { + Filterable, + Sortable, + Selectable, + Groupable, + Collapsible, + Expandable, + Editable, + Always, +} + +type Item = { + title: string; + subTitle: string; + action: ItemAction; + active: boolean; + completed: boolean; +}; + +const makeItem = ( + title: string, + subTitle: string, + completed: boolean, + action: ItemAction +): Item => ({ title, subTitle, completed, action, active: action === ItemAction.Always }); + +const theadKeyCombinations: Item[] = [ + makeItem('Space', 'select column', false, ItemAction.Selectable), + makeItem('Ctrl + ↑/↓', 'sort column asc/desc', false, ItemAction.Sortable), + makeItem('Shift + Alt + ←/→', 'group / ungroup column', false, ItemAction.Groupable), + makeItem('Alt + arrows', 'expand / collapse column group', false, ItemAction.Collapsible), + makeItem('Ctrl + Shift + L', 'open Excel-style filter', false, ItemAction.Filterable), + makeItem('Alt + L', 'open Advanced filtering', false, ItemAction.Filterable), ]; -mods.forEach((m) => m.register()); -export default class Sample extends React.Component { - private grid: IgrGrid - private gridRef(r: IgrGrid) { - this.grid = r; - this.setState({}); - } +const tbodyKeyCombinations: Item[] = [ + makeItem('Enter', 'enter edit mode', false, ItemAction.Editable), + makeItem('Alt + ←/→', 'expand/collapse group row', false, ItemAction.Expandable), + makeItem('Ctrl + Home/End', 'jump to first/last cell', false, ItemAction.Always), + makeItem('Alt + →/↓', 'expand row detail', false, ItemAction.Collapsible), + makeItem('Alt + ←/↑', 'collapse row detail', false, ItemAction.Collapsible), +]; - constructor(props: any) { - super(props); +const summaryCombinations: Item[] = [ + makeItem('←', 'move left', false, ItemAction.Always), + makeItem('→', 'move right', false, ItemAction.Always), + makeItem('Home', 'first summary cell', false, ItemAction.Always), + makeItem('End', 'last summary cell', false, ItemAction.Always), +]; - this.gridRef = this.gridRef.bind(this); - this.webGridCustomKBNav = this.webGridCustomKBNav.bind(this); - } +const cloneItems = (items: Item[]) => items.map(i => ({ ...i })); - public render(): JSX.Element { - return ( -
- -
- - - - - - - - - - - - - - -
+function enableActions(list: Item[], actions: ItemAction[]) { + return list.map(i => + i.action === ItemAction.Always + ? { ...i, active: true } + : { ...i, active: actions.includes(i.action) } + ); +} + +function markCompleted(list: Item[], idx: number, value: boolean) { + const next = list.slice(); + next[idx] = { ...next[idx], completed: value }; + return next; +} + +function withComputed(data: Customer[]): Customer[] { + return data.map((item) => { + const profit = (100000 + Math.random() * 1000000); + return { + ...item, + CompanysAnnualProfit: profit, + }; + }); +} + +const MasterDetailTemplate: React.FC = ({ dataContext }) => { + const rowData = dataContext.implicit as Customer; + + return ( +
+

Product Details

+
+
+ Product ID: {rowData.ProductID}
+ Supplier ID: {rowData.SupplierID}
+ Category ID: {rowData.CategoryID}
+ Unit Price: ${rowData.UnitPrice?.toFixed(2)}
+
+
+ Units in Stock: {rowData.UnitsInStock}
+ Units on Order: {rowData.UnitsOnOrder}
+ Reorder Level: {rowData.ReorderLevel}
+ Discontinued: {rowData.Discontinued ? 'Yes' : 'No'}
+
+
+ {rowData.OrderDate && ( +
+ Order Date: {new Date(rowData.OrderDate).toLocaleDateString()} +
+ )} + {rowData.CompanysAnnualProfit && ( +
+ Annual Profit: ${rowData.CompanysAnnualProfit.toLocaleString()}
- ); + )} +
+ ); +}; + +export default function GridKeyboardNavGuide() { + const gridRef = useRef(null); + + const [data, setData] = useState(() => withComputed(NwindData as Customer[])); + const [gridSection, setGridSection] = useState(GridSection.THEAD); + const [items, setItems] = useState(cloneItems(theadKeyCombinations)); + + useEffect(() => { + const handleGlobalKeyDown = (evt: KeyboardEvent) => { + const key = evt.key?.toLowerCase(); + + const gridElement = (gridRef.current as any)?.nativeElement; + const focusedElement = document.activeElement; + + if (gridElement && (gridElement.contains(focusedElement) || gridElement === focusedElement)) { + if (evt.altKey && (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright')) { + if (gridSection === GridSection.TBODY) { + if (key === 'arrowup' || key === 'arrowleft') { + setTimeout(() => { + setItems((prev: Item[]) => markCompleted(prev, 4, true)); + }, 0); + } else if (key === 'arrowdown' || key === 'arrowright') { + setTimeout(() => { + setItems((prev: Item[]) => markCompleted(prev, 3, true)); + }, 0); + } + } + } + + if (evt.ctrlKey && (key === 'home' || key === 'end')) { + if (gridSection === GridSection.TBODY) { + setTimeout(() => { + setItems((prev: Item[]) => markCompleted(prev, 2, true)); + }, 0); + } + } + } + }; + + document.addEventListener('keydown', handleGlobalKeyDown, true); + + return () => { + document.removeEventListener('keydown', handleGlobalKeyDown, true); + }; + }, [gridSection]); + + const headerText = useMemo(() => { + if (gridSection === GridSection.THEAD) return 'HEADER COMBINATIONS'; + if (gridSection === GridSection.TBODY) return 'BODY COMBINATIONS'; + if (gridSection === GridSection.FOOTER) return 'SUMMARY COMBINATIONS'; + return ''; + }, [gridSection]); + + const updateSectionFromActiveNode = () => { + const grid = gridRef.current; + const active = (grid as any)?.navigation?.activeNode; + const rows = grid?.data?.length ?? data.length; + if (active && typeof active.row === 'number') { + if (active.row < 0) { + setGridSection(GridSection.THEAD); + setItems(cloneItems(theadKeyCombinations)); + } else if (active.row >= rows) { + setGridSection(GridSection.FOOTER); + setItems(cloneItems(summaryCombinations)); + } else { + setGridSection(GridSection.TBODY); + setItems(cloneItems(tbodyKeyCombinations)); + } } + }; + + const onGridKeyDown = (evt: any) => { + const key = evt.key?.toLowerCase(); + if (!key) return; + if (key === 'tab') return; - private _nwindData: any[] = NwindData; - public get nwindData(): any[] { - return this._nwindData; + if (evt.altKey && (key === 'arrowleft' || key === 'arrowright' || key === 'arrowup' || key === 'arrowdown')) { + evt.preventDefault(); } - private _componentRenderer: ComponentRenderer = null; - public get renderer(): ComponentRenderer { - if (this._componentRenderer == null) { - this._componentRenderer = new ComponentRenderer(); - var context = this._componentRenderer.context; - PropertyEditorPanelDescriptionModule.register(context); - WebGridDescriptionModule.register(context); - } - return this._componentRenderer; + if ((key === 'l' && evt.altKey) || + (key === 'l' && evt.ctrlKey && evt.shiftKey) || + ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) || + ((key === 'end' || key === 'home') && evt.ctrlKey)) { + evt.preventDefault(); } - public webGridCustomKBNav(eventArgs: IgrGridKeydownEventArgs): void { - const args = eventArgs.detail; - const target = args.target; - const evt = args.event; - const type = args.targetType; - const grid = eventArgs.target as IgrGrid; - - if (type === 'dataCell' && target.editMode && evt.key.toLowerCase() === 'tab') { - // Value validation for number column. - // This covers both 'tab' and 'shift+tab' key interactions. - args.event.preventDefault(); - args.cancel = true; - if (target.column.dataType === 'number' && target.editValue < 10) { - alert('The value should be bigger than 10'); - return; - } - const cell = evt.shiftKey ? - grid.getPreviousCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable) : - grid.getNextCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable); - - grid.navigateTo(cell.rowIndex, cell.visibleColumnIndex, - (obj: any) => { obj.target.activate(); }); - } else if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') { - // Perform column based kb navigation with 'enter' key press - args.cancel = true; - grid.navigateTo(target.row.index + 1, target.column.visibleIndex, (obj: any) => { - obj.target.activate(); - }); + updateSectionFromActiveNode(); + + setItems((prev: Item[]) => { + let next = prev.slice(); + + if (gridSection === GridSection.FOOTER) { + switch (key) { + case 'end': + next = markCompleted(next, 3, true); + break; + case 'home': + next = markCompleted(next, 2, true); + break; + case 'arrowleft': + next = markCompleted(next, 0, true); + break; + case 'arrowright': + next = markCompleted(next, 1, true); + break; + } + return next; + } + + if (gridSection === GridSection.THEAD) { + if (key === 'l' && evt.altKey) { + next = markCompleted(next, 5, true); + return next; } + if (key === 'l' && evt.ctrlKey && evt.shiftKey) { + next = markCompleted(next, 4, true); + } + if ((key === 'arrowleft' || key === 'arrowright') && evt.altKey && evt.shiftKey) { + next = markCompleted(next, 2, true); + } + if ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) { + next = markCompleted(next, 1, true); + } + if (key === ' ' || key === 'spacebar') { + next = markCompleted(next, 0, true); + } + } + + if (gridSection === GridSection.TBODY) { + if (key === 'enter') { + next = markCompleted(next, 0, true); + } + if ((key === 'end' || key === 'home') && evt.ctrlKey) { + next = markCompleted(next, 2, true); + } + } + + return next; + }); + }; + + const refreshHeaderActions = () => { + const grid: any = gridRef.current; + if (!grid) return; + + if (gridSection !== GridSection.THEAD) return; + + const active = grid?.navigation?.activeNode; + const col = grid.visibleColumns?.find( + (c: any) => c.visibleIndex === active?.column && c.level === active?.level + ); + + const actions: ItemAction[] = []; + if (col?.sortable) actions.push(ItemAction.Sortable); + if (col?.filterable && !col?.columnGroup) actions.push(ItemAction.Filterable); + if (col?.collapsible) actions.push(ItemAction.Collapsible); + if (col?.groupable) actions.push(ItemAction.Groupable); + if (col?.selectable) actions.push(ItemAction.Selectable); + + setItems((prev: Item[]) => enableActions(prev, actions)); + }; + + // When grid focuses a new node, update section + header actions + const onActiveNodeChange = () => { + updateSectionFromActiveNode(); + refreshHeaderActions(); + }; + + const onRowToggle = () => { + if (gridSection === GridSection.TBODY) { + setItems((prev: Item[]) => markCompleted(prev, 1, true)); + } + }; + + const onColumnSelectionChanging = (e: any) => { + if (e?.detail?.event?.type === 'keydown') { + setItems((prev: Item[]) => markCompleted(prev, 0, true)); } + }; + + return ( +
+
+ + + {/* Toolbar can be conditionally shown if needed */} + {/* */} + + {/* Row detail template removed for simplicity */} + + {/* Column Groups */} + + + + + + + + + + + + + + + + + + + +
+ + {/* Right-side list showing active/available shortcuts */} +
+ + {items.length > 0 && ( + + {headerText} + + )} + {items.map((c: Item, idx: number) => ( + +
+
+

{c.title}

+

{c.subTitle}

+
+ { + const checked = !!e?.detail; + setItems((prev: Item[]) => markCompleted(prev, idx, checked)); + }} + /> +
+
+ ))} + + {items.length === 0 && ( +
+
Use the browser navigation until you reach one of the following grid sections:
+
    +
  • Header
  • +
  • Body
  • +
  • Summary
  • +
+
When reached, an action list will be shown.
+
+ )} +
+
+
+ ); } -// rendering above component in the React DOM -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render(); \ No newline at end of file +// Render to DOM +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render(); diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/.eslintrc.js b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/.eslintrc.js new file mode 100644 index 0000000000..7168b71441 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/.eslintrc.js @@ -0,0 +1,78 @@ +// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project +module.exports = { + parser: "@typescript-eslint/parser", // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module", // Allows for the use of imports + ecmaFeatures: { + jsx: true // Allows for the parsing of JSX + } + }, + settings: { + react: { + version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use + } + }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react + "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-prototype-builtins": "off", + "no-mixed-spaces-and-tabs": 0, + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-var": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-mixed-spaces-and-tabs": 0, + "no-prototype-builtins": "off", + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } + } + ] + }; \ No newline at end of file diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md new file mode 100644 index 0000000000..ed2f5e5fc6 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md @@ -0,0 +1,56 @@ + + + +This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## Branches + +> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository. + +## Instructions + +Follow these instructions to run this example: + + +``` +git clone https://github.com/IgniteUI/igniteui-react-examples.git +git checkout master +cd ./igniteui-react-examples +cd ./samples/grids/grid/keyboard-custom-navigation +``` + +open above folder in VS Code or type: +``` +code . +``` + +In terminal window, run: +``` +npm install --legacy-peer-deps +npm run-script start +``` + +Then open http://localhost:4200/ in your browser + + +## Learn More + +To learn more about **Ignite UI for React** components, check out the [React documentation](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html). diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/package.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/package.json new file mode 100644 index 0000000000..9246bcd77a --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/package.json @@ -0,0 +1,48 @@ +{ + "name": "example-ignite-ui-react", + "description": "This project provides example of using Ignite UI for React components", + "author": "Infragistics", + "version": "1.4.0", + "license": "", + "homepage": ".", + "private": true, + "scripts": { + "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start", + "build": "react-scripts --max_old_space_size=10240 build ", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject", + "lint": "eslint ./src/**/*.{ts,tsx}" + }, + "dependencies": { + "igniteui-dockmanager": "^1.17.0", + "igniteui-react": "^19.2.0", + "igniteui-react-core": "^19.0.1", + "igniteui-react-grids": "^19.2.0", + "igniteui-react-inputs": "^19.0.1", + "igniteui-react-layouts": "^19.0.1", + "igniteui-webcomponents": "^6.2.0", + "lit-html": "^3.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "^5.0.1", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@types/jest": "^29.2.0", + "@types/node": "^18.11.7", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "eslint": "^8.33.0", + "eslint-config-react": "^1.1.7", + "eslint-plugin-react": "^7.20.0", + "react-app-rewired": "^2.2.1", + "typescript": "^4.8.4", + "worker-loader": "^3.0.8" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/public/index.html b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/public/index.html new file mode 100644 index 0000000000..e2d3265576 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/public/index.html @@ -0,0 +1,11 @@ + + + + Sample | Ignite UI | React | infragistics + + + + +
+ + \ No newline at end of file diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/sandbox.config.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/sandbox.config.json new file mode 100644 index 0000000000..07f53508eb --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/sandbox.config.json @@ -0,0 +1,5 @@ +{ + "infiniteLoopProtection": false, + "hardReloadOnChange": false, + "view": "browser" +} \ No newline at end of file diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/NwindData.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/NwindData.json new file mode 100644 index 0000000000..c00b03ec8d --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/NwindData.json @@ -0,0 +1,458 @@ +[ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2012-02-12", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": true, + "OrderDate": "2003-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2006-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 4, + "ProductName": "Chef Antons Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2016-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 5, + "ProductName": "Chef Antons Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2011-11-11", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 6, + "ProductName": "Grandmas Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2017-12-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 7, + "ProductName": "Uncle Bobs Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 150, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2016-07-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2018-01-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2010-02-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2008-05-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": false, + "OrderDate": "2009-01-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2015-11-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2015-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2017-06-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2014-03-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + }, + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2018-03-28", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2015-08-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-09-27", + "Rating": 2, + "Locations": [ + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + }, + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2001-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + } + ] + }, + { + "ProductID": 20, + "ProductName": "Sir Rodneys Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "4 - 100 ml jars", + "UnitPrice": 4.5, + "UnitsInStock": 40, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + } +] \ No newline at end of file diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/SingersData.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/SingersData.json new file mode 100644 index 0000000000..75ec4ec561 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/SingersData.json @@ -0,0 +1,2496 @@ +[ + { + "ID": 0, + "Artist": "Naomí Yepes", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/naomi.jpg", + "Debut": 2011, + "GrammyNominations": 6, + "GrammyAwards": 0, + "HasGrammyAward": false, + "Tours": [ + { + "Tour": "Faithful Tour", + "StartedOn": "Sep 12", + "Location": "Worldwide", + "Headliner": "NO", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "City Jam Sessions", + "StartedOn": "Aug 13", + "Location": "North America", + "Headliner": "YES", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "Christmas NYC 2013", + "StartedOn": "Dec 13", + "Location": "United States", + "Headliner": "NO", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "Christmas NYC 2014", + "StartedOn": "Dec 14", + "Location": "North America", + "Headliner": "NO", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "Watermelon Tour", + "StartedOn": "Feb 15", + "Location": "Worldwide", + "Headliner": "YES", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "Christmas NYC 2016", + "StartedOn": "Dec 16", + "Location": "United States", + "Headliner": "NO", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "The Dragon Tour", + "StartedOn": "Feb 17", + "Location": "Worldwide", + "Headliner": "NO", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "Organic Sessions", + "StartedOn": "Aug 18", + "Location": "United States, England", + "Headliner": "YES", + "TouredBy": "Naomí Yepes" + }, + { + "Tour": "Hope World Tour", + "StartedOn": "Mar 19", + "Location": "Worldwide", + "Headliner": "NO", + "TouredBy": "Naomí Yepes" + } + ], + "Albums": [ + { + "Album": "Initiation", + "LaunchDate": "September 3, 2013", + "BillboardReview": 86, + "USBillboard200": 1, + "Artist": "Naomí Yepes", + "Songs": [ + { + "Number": 1, + "Title": "Lonely Falling", + "Released": "01 May 2019", + "Genre": "*", + "Album": "Initiation" + }, + { + "Number": 2, + "Title": "Bright Breaking", + "Released": "25 Jul 2019", + "Genre": "Electro house Electropop", + "Album": "Initiation" + }, + { + "Number": 3, + "Title": "River of Whisper", + "Released": "29 Jan 2020", + "Genre": "Electro house Electropop", + "Album": "Initiation" + }, + { + "Number": 4, + "Title": "Sky of Storm", + "Released": "07 May 2019", + "Genre": "Electro house Electropop", + "Album": "Initiation" + }, + { + "Number": 5, + "Title": "Electric River", + "Released": "20 Jan 2020", + "Genre": "R&B", + "Album": "Initiation" + }, + { + "Number": 6, + "Title": "Storm of Storm", + "Released": "01 Sep 2019", + "Genre": "Crunk reggaeton", + "Album": "Initiation" + }, + { + "Number": 7, + "Title": "Fire of Dream", + "Released": "12 Sep 2019", + "Genre": "Electro house Electropop", + "Album": "Initiation" + }, + { + "Number": 8, + "Title": "Burning in the Light", + "Released": "15 Apr 2019", + "Genre": "*", + "Album": "Initiation" + }, + { + "Number": 9, + "Title": "Burning in the Storm", + "Released": "10 Sep 2019", + "Genre": "Electro house Electropop", + "Album": "Initiation" + }, + { + "Number": 10, + "Title": "Shadow of Whisper", + "Released": "06 Jan 2019", + "Genre": "Crunk reggaeton", + "Album": "Initiation" + } + ] + }, + { + "Album": "Dream Driven", + "LaunchDate": "August 25, 2014", + "BillboardReview": 81, + "USBillboard200": 1, + "Artist": "Naomí Yepes", + "Songs": [ + { + "Number": 1, + "Title": "Intro", + "Released": "10 Sep 2019", + "Genre": "*", + "Album": "Dream Driven" + }, + { + "Number": 2, + "Title": "Ferocious", + "Released": "28 Apr 2014", + "Genre": "Dance-pop R&B", + "Album": "Dream Driven" + }, + { + "Number": 3, + "Title": "Going crazy", + "Released": "10 Feb 2015", + "Genre": "Dance-pop EDM", + "Album": "Dream Driven" + }, + { + "Number": 4, + "Title": "Future past", + "Released": "20 Sep 2011", + "Genre": "*", + "Album": "Dream Driven" + }, + { + "Number": 5, + "Title": "Roaming like them", + "Released": "2 Jul 2014", + "Genre": "Electro house Electropop", + "Album": "Dream Driven" + }, + { + "Number": 6, + "Title": "Last Wishes", + "Released": "12 Aug 2014", + "Genre": "R&B", + "Album": "Dream Driven" + }, + { + "Number": 7, + "Title": "Stay where you are", + "Released": "14 Sep 2013", + "Genre": "*", + "Album": "Dream Driven" + }, + { + "Number": 8, + "Title": "Imaginarium", + "Released": "20 Sep 2021", + "Genre": "*", + "Album": "Dream Driven" + }, + { + "Number": 9, + "Title": "Tell me", + "Released": "30 Sep 2014", + "Genre": "Synth-pop R&B", + "Album": "Dream Driven" + }, + { + "Number": 10, + "Title": "Shredded into pieces", + "Released": "11 Sep 2015", + "Genre": "*", + "Album": "Dream Driven" + }, + { + "Number": 11, + "Title": "Capture this moment", + "Released": "13 Sep 2016", + "Genre": "*", + "Album": "Dream Driven" + }, + { + "Number": 12, + "Title": "Dream Driven", + "Released": "14 Sep 2014", + "Genre": "*", + "Album": "Dream Driven" + } + ] + }, + { + "Album": "The dragon journey", + "LaunchDate": "May 20, 2016", + "BillboardReview": 60, + "USBillboard200": 2, + "Artist": "Naomí Yepes", + "Songs": [ + { + "Number": 1, + "Title": "Calling in the Storm", + "Released": "12 Mar 2019", + "Genre": "Electro house Electropop", + "Album": "The dragon journey" + }, + { + "Number": 2, + "Title": "Hiding in the Dream", + "Released": "23 Mar 2019", + "Genre": "R&B", + "Album": "The dragon journey" + }, + { + "Number": 3, + "Title": "Electric Heart", + "Released": "17 Mar 2019", + "Genre": "ethno-tunes", + "Album": "The dragon journey" + }, + { + "Number": 4, + "Title": "Shadow of Echo", + "Released": "20 Feb 2019", + "Genre": "ethno-tunes", + "Album": "The dragon journey" + }, + { + "Number": 5, + "Title": "Flying in the Storm", + "Released": "08 Apr 2019", + "Genre": "R&B", + "Album": "The dragon journey" + }, + { + "Number": 6, + "Title": "Dark Waiting", + "Released": "20 Oct 2019", + "Genre": "Synth-pop R&B", + "Album": "The dragon journey" + }, + { + "Number": 7, + "Title": "Fire of River", + "Released": "20 Feb 2019", + "Genre": "Synth-pop R&B", + "Album": "The dragon journey" + }, + { + "Number": 8, + "Title": "Wild Crying", + "Released": "14 Jun 2019", + "Genre": "R&B", + "Album": "The dragon journey" + }, + { + "Number": 9, + "Title": "Bright Dancing", + "Released": "14 Mar 2019", + "Genre": "Electro house Electropop", + "Album": "The dragon journey" + }, + { + "Number": 10, + "Title": "Golden Waiting", + "Released": "12 Sep 2019", + "Genre": "Synth-pop R&B", + "Album": "The dragon journey" + } + ] + }, + { + "Album": "Organic me", + "LaunchDate": "August 17, 2018", + "BillboardReview": 82, + "USBillboard200": 1, + "Artist": "Naomí Yepes", + "Songs": [ + { + "Number": 1, + "Title": "I Love", + "Released": "11 May 2019", + "Genre": "Crunk reggaeton", + "Album": "Organic me" + }, + { + "Number": 2, + "Title": "Early Morning Compass", + "Released": "15 Jan 2020", + "Genre": "mystical parody-bap ", + "Album": "Organic me" + }, + { + "Number": 3, + "Title": "Key Fields Forever", + "Released": "2 Jan 2020", + "Genre": "Dance-pop EDM", + "Album": "Organic me" + }, + { + "Number": 4, + "Title": "Stand by Your Goblins", + "Released": "20 Nov 2019", + "Genre": "*", + "Album": "Organic me" + }, + { + "Number": 5, + "Title": "Mad to Walk", + "Released": "12 May 2019", + "Genre": "Electro house Electropop", + "Album": "Organic me" + }, + { + "Number": 6, + "Title": "Alice's Waiting", + "Released": "28 Jan 2020", + "Genre": "R&B", + "Album": "Organic me" + }, + { + "Number": 7, + "Title": "We Shall Kiss", + "Released": "30 Oct 2019", + "Genre": "*", + "Album": "Organic me" + }, + { + "Number": 8, + "Title": "Behind Single Ants", + "Released": "2 Oct 2019", + "Genre": "*", + "Album": "Organic me" + }, + { + "Number": 9, + "Title": "Soap Autopsy", + "Released": "8 Aug 2019", + "Genre": "Synth-pop R&B", + "Album": "Organic me" + }, + { + "Number": 10, + "Title": "Have You Met Rich?", + "Released": "1 Jul 2019", + "Genre": "ethno-tunes", + "Album": "Organic me" + }, + { + "Number": 11, + "Title": "Livin' on a Banana", + "Released": "22 Nov 2019", + "Genre": "Crunk reggaeton", + "Album": "Organic me" + } + ] + }, + { + "Album": "Curiosity", + "LaunchDate": "December 7, 2019", + "BillboardReview": 75, + "USBillboard200": 12, + "Artist": "Naomí Yepes", + "Songs": [ + { + "Number": 1, + "Title": "Storm of Dream", + "Released": "05 Jan 2019", + "Genre": "*", + "Album": "Curiosity" + }, + { + "Number": 2, + "Title": "Echo of Dream", + "Released": "28 Jan 2019", + "Genre": "Synth-pop R&B", + "Album": "Curiosity" + }, + { + "Number": 3, + "Title": "Light of Shadow", + "Released": "07 Feb 2019", + "Genre": "Synth-pop R&B", + "Album": "Curiosity" + }, + { + "Number": 4, + "Title": "Storm of Heart", + "Released": "05 Jan 2020", + "Genre": "Electro house Electropop", + "Album": "Curiosity" + }, + { + "Number": 5, + "Title": "Shadow of River", + "Released": "27 Feb 2019", + "Genre": "*", + "Album": "Curiosity" + }, + { + "Number": 6, + "Title": "Wicked Dancing", + "Released": "17 Jan 2020", + "Genre": "ethno-tunes", + "Album": "Curiosity" + }, + { + "Number": 7, + "Title": "River of Light", + "Released": "22 Feb 2019", + "Genre": "R&B", + "Album": "Curiosity" + }, + { + "Number": 8, + "Title": "Lonely Breaking", + "Released": "09 Sep 2019", + "Genre": "ethno-tunes", + "Album": "Curiosity" + }, + { + "Number": 9, + "Title": "Furious Flying", + "Released": "08 Jun 2019", + "Genre": "R&B", + "Album": "Curiosity" + }, + { + "Number": 10, + "Title": "Hiding in the Storm", + "Released": "27 May 2019", + "Genre": "Electro house Electropop", + "Album": "Curiosity" + } + ] + } + ] + }, + { + "ID": 1, + "Artist": "Babila Ebwélé", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/babila.jpg", + "Debut": 2009, + "GrammyNominations": 0, + "GrammyAwards": 11, + "HasGrammyAward": true, + "Tours": [ + { + "Tour": "The last straw", + "StartedOn": "May 09", + "Location": "Europe, Asia", + "Headliner": "NO", + "TouredBy": "Babila Ebwélé" + }, + { + "Tour": "No foundations", + "StartedOn": "Jun 04", + "Location": "United States, Europe", + "Headliner": "YES", + "TouredBy": "Babila Ebwélé" + }, + { + "Tour": "Crazy eyes", + "StartedOn": "Jun 08", + "Location": "North America", + "Headliner": "NO", + "TouredBy": "Babila Ebwélé" + }, + { + "Tour": "Zero gravity", + "StartedOn": "Apr 19", + "Location": "United States", + "Headliner": "NO", + "TouredBy": "Babila Ebwélé" + }, + { + "Tour": "Battle with myself", + "StartedOn": "Mar 08", + "Location": "North America", + "Headliner": "YES", + "TouredBy": "Babila Ebwélé" + } + ], + "Albums": [ + { + "Album": "Pushing up daisies", + "LaunchDate": "May 31, 2000", + "BillboardReview": 86, + "USBillboard200": 42, + "Artist": "Babila Ebwélé", + "Songs": [ + { + "Number": 1, + "Title": "Wood Shavings Forever", + "Released": "9 Jun 2019", + "Genre": "*", + "Album": "Pushing up daisies" + }, + { + "Number": 2, + "Title": "Early Morning Drive", + "Released": "20 May 2019", + "Genre": "*", + "Album": "Pushing up daisies" + }, + { + "Number": 3, + "Title": "Don't Natter", + "Released": "10 Jun 2019", + "Genre": "adult calypso-industrial", + "Album": "Pushing up daisies" + }, + { + "Number": 4, + "Title": "Stairway to Balloons", + "Released": "18 Jun 2019", + "Genre": "calypso and mariachi", + "Album": "Pushing up daisies" + }, + { + "Number": 5, + "Title": "The Number of your Apple", + "Released": "29 Oct 2019", + "Genre": "*", + "Album": "Pushing up daisies" + }, + { + "Number": 6, + "Title": "Your Delightful Heart", + "Released": "24 Feb 2019", + "Genre": "*", + "Album": "Pushing up daisies" + }, + { + "Number": 7, + "Title": "Nice Weather For Balloons", + "Released": "1 Aug 2019", + "Genre": "rap-hop", + "Album": "Pushing up daisies" + }, + { + "Number": 8, + "Title": "The Girl From Cornwall", + "Released": "4 May 2019", + "Genre": "enigmatic rock-and-roll", + "Album": "Pushing up daisies" + }, + { + "Number": 9, + "Title": "Here Without Jack", + "Released": "24 Oct 2019", + "Genre": "*", + "Album": "Pushing up daisies" + }, + { + "Number": 10, + "Title": "Born Rancid", + "Released": "19 Mar 2019", + "Genre": "*", + "Album": "Pushing up daisies" + } + ] + }, + { + "Album": "Death's dead", + "LaunchDate": "June 8, 2016", + "BillboardReview": 85, + "USBillboard200": 95, + "Artist": "Babila Ebwélé", + "Songs": [ + { + "Number": 1, + "Title": "Men Sound Better With You", + "Released": "20 Oct 2019", + "Genre": "rap-hop", + "Album": "Death's dead" + }, + { + "Number": 2, + "Title": "Ghost in My Rod", + "Released": "5 Oct 2019", + "Genre": "enigmatic rock-and-roll", + "Album": "Death's dead" + }, + { + "Number": 3, + "Title": "Bed of Men", + "Released": "14 Nov 2019", + "Genre": "whimsical comedy-grass ", + "Album": "Death's dead" + }, + { + "Number": 4, + "Title": "Don't Push", + "Released": "2 Jan 2020", + "Genre": "unblack electronic-trip-hop", + "Album": "Death's dead" + }, + { + "Number": 5, + "Title": "Nice Weather For Men", + "Released": "18 Dec 2019", + "Genre": "*", + "Album": "Death's dead" + }, + { + "Number": 6, + "Title": "Rancid Rhapsody", + "Released": "10 Mar 2019", + "Genre": "*", + "Album": "Death's dead" + }, + { + "Number": 7, + "Title": "Push, Push, Push!", + "Released": "21 Feb 2019", + "Genre": "*", + "Album": "Death's dead" + }, + { + "Number": 8, + "Title": "My Name is Sarah", + "Released": "15 Nov 2019", + "Genre": "*", + "Album": "Death's dead" + }, + { + "Number": 9, + "Title": "The Girl From My Hotel", + "Released": "6 Nov 2019", + "Genre": "*", + "Album": "Death's dead" + }, + { + "Number": 10, + "Title": "Free Box", + "Released": "18 Apr 2019", + "Genre": "splitter-funk", + "Album": "Death's dead" + }, + { + "Number": 11, + "Title": "Hotel Cardiff", + "Released": "30 Dec 2019", + "Genre": "guilty pleasure ebm", + "Album": "Death's dead" + } + ] + } + ] + }, + { + "ID": 2, + "Artist": "Ahmad Nazeri", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/ahmad.jpg", + "Debut": 2004, + "GrammyNominations": 3, + "GrammyAwards": 1, + "HasGrammyAward": true, + "Tours": [ + + ], + "Albums": [ + { + "Album": "Emergency", + "LaunchDate": "March 6, 2004", + "BillboardReview": 98, + "USBillboard200": 69, + "Artist": "Ahmad Nazeri", + "Songs": [ + { + "Number": 1, + "Title": "Gentle Falling", + "Released": "26 Apr 2019", + "Genre": "Crunk reggaeton", + "Album": "Emergency" + }, + { + "Number": 2, + "Title": "Calling in the Fire", + "Released": "03 Sep 2019", + "Genre": "ethno-tunes", + "Album": "Emergency" + }, + { + "Number": 3, + "Title": "Fire of Shadow", + "Released": "05 Jan 2019", + "Genre": "ethno-tunes", + "Album": "Emergency" + }, + { + "Number": 4, + "Title": "Dancing in the Dream", + "Released": "15 Apr 2019", + "Genre": "R&B", + "Album": "Emergency" + }, + { + "Number": 5, + "Title": "Calling in the Shadow", + "Released": "09 Oct 2019", + "Genre": "R&B", + "Album": "Emergency" + }, + { + "Number": 6, + "Title": "Falling in the Sky", + "Released": "08 Mar 2019", + "Genre": "ethno-tunes", + "Album": "Emergency" + }, + { + "Number": 7, + "Title": "Calling in the Storm", + "Released": "05 Dec 2019", + "Genre": "ethno-tunes", + "Album": "Emergency" + }, + { + "Number": 8, + "Title": "Falling in the River", + "Released": "19 Aug 2019", + "Genre": "Electro house Electropop", + "Album": "Emergency" + }, + { + "Number": 9, + "Title": "Electric Fire", + "Released": "30 Nov 2019", + "Genre": "Crunk reggaeton", + "Album": "Emergency" + }, + { + "Number": 10, + "Title": "Lonely River", + "Released": "11 Nov 2019", + "Genre": "Electro house Electropop", + "Album": "Emergency" + } + ] + }, + { + "Album": "Bursting bubbles", + "LaunchDate": "April 17, 2006", + "BillboardReview": 69, + "USBillboard200": 39, + "Artist": "Ahmad Nazeri", + "Songs": [ + { + "Number": 1, + "Title": "Lonely Dream", + "Released": "11 Dec 2019", + "Genre": "ethno-tunes", + "Album": "Bursting bubbles" + }, + { + "Number": 2, + "Title": "Fire of River", + "Released": "01 Aug 2019", + "Genre": "Synth-pop R&B", + "Album": "Bursting bubbles" + }, + { + "Number": 3, + "Title": "Wicked Falling", + "Released": "25 Jan 2019", + "Genre": "*", + "Album": "Bursting bubbles" + }, + { + "Number": 4, + "Title": "Crying in the Shadow", + "Released": "04 Jan 2019", + "Genre": "Synth-pop R&B", + "Album": "Bursting bubbles" + }, + { + "Number": 5, + "Title": "Wild Burning", + "Released": "10 May 2019", + "Genre": "ethno-tunes", + "Album": "Bursting bubbles" + }, + { + "Number": 6, + "Title": "Waiting in the Heart", + "Released": "07 Aug 2019", + "Genre": "ethno-tunes", + "Album": "Bursting bubbles" + }, + { + "Number": 7, + "Title": "Fire of Fire", + "Released": "16 May 2019", + "Genre": "Electro house Electropop", + "Album": "Bursting bubbles" + }, + { + "Number": 8, + "Title": "Bright Heart", + "Released": "14 Mar 2019", + "Genre": "Synth-pop R&B", + "Album": "Bursting bubbles" + }, + { + "Number": 9, + "Title": "Lonely Fire", + "Released": "15 Oct 2019", + "Genre": "R&B", + "Album": "Bursting bubbles" + }, + { + "Number": 10, + "Title": "Sky of Dream", + "Released": "20 Jun 2019", + "Genre": "ethno-tunes", + "Album": "Bursting bubbles" + } + ] + } + ] + }, + { + "ID": 3, + "Artist": "Kimmy McIlmorie", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/kimmy.jpg", + "Debut": 2007, + "GrammyNominations": 21, + "GrammyAwards": 3, + "HasGrammyAward": true, + "Tours": [ + + ], + "Albums": [ + { + "Album": "Here we go again", + "LaunchDate": "November 18, 2017", + "BillboardReview": 68, + "USBillboard200": 1, + "Artist": "Kimmy McIlmorie", + "Songs": [ + { + "Number": 1, + "Title": "Storm of Sky", + "Released": "04 Mar 2019", + "Genre": "ethno-tunes", + "Album": "Here we go again" + }, + { + "Number": 2, + "Title": "Dream of Shadow", + "Released": "03 Jan 2019", + "Genre": "ethno-tunes", + "Album": "Here we go again" + }, + { + "Number": 3, + "Title": "Dream of Shadow", + "Released": "19 Dec 2019", + "Genre": "Electro house Electropop", + "Album": "Here we go again" + }, + { + "Number": 4, + "Title": "Golden Fire", + "Released": "20 Jan 2019", + "Genre": "R&B", + "Album": "Here we go again" + }, + { + "Number": 5, + "Title": "Running in the Light", + "Released": "03 Jan 2020", + "Genre": "Synth-pop R&B", + "Album": "Here we go again" + }, + { + "Number": 6, + "Title": "Flying in the Heart", + "Released": "17 Jan 2019", + "Genre": "*", + "Album": "Here we go again" + }, + { + "Number": 7, + "Title": "Fire of Storm", + "Released": "26 Jan 2019", + "Genre": "ethno-tunes", + "Album": "Here we go again" + }, + { + "Number": 8, + "Title": "Calling in the Sky", + "Released": "28 Oct 2019", + "Genre": "Synth-pop R&B", + "Album": "Here we go again" + }, + { + "Number": 9, + "Title": "Flying in the Shadow", + "Released": "30 Mar 2019", + "Genre": "ethno-tunes", + "Album": "Here we go again" + }, + { + "Number": 10, + "Title": "Golden Dancing", + "Released": "12 Oct 2019", + "Genre": "ethno-tunes", + "Album": "Here we go again" + } + ] + } + ] + }, + { + "ID": 4, + "Artist": "Mar Rueda", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/mar.jpg", + "Debut": 1996, + "GrammyNominations": 14, + "GrammyAwards": 2, + "HasGrammyAward": true, + "Tours": [ + + ], + "Albums": [ + + ] + }, + { + "ID": 5, + "Artist": "Izabella Tabakova", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/izabella.jpg", + "Debut": 2017, + "GrammyNominations": 7, + "GrammyAwards": 11, + "HasGrammyAward": true, + "Tours": [ + { + "Tour": "Final breath", + "StartedOn": "Jun 13", + "Location": "Europe", + "Headliner": "YES", + "TouredBy": "Izabella Tabakova" + }, + { + "Tour": "Once bitten", + "StartedOn": "Dec 18", + "Location": "Australia, United States", + "Headliner": "NO", + "TouredBy": "Izabella Tabakova" + }, + { + "Tour": "Code word", + "StartedOn": "Sep 19", + "Location": "United States, Europe", + "Headliner": "NO", + "TouredBy": "Izabella Tabakova" + }, + { + "Tour": "Final draft", + "StartedOn": "Sep 17", + "Location": "United States, Europe", + "Headliner": "YES", + "TouredBy": "Izabella Tabakova" + } + ], + "Albums": [ + { + "Album": "Once bitten", + "LaunchDate": "July 16, 2007", + "BillboardReview": 79, + "USBillboard200": 53, + "Artist": "Izabella Tabakova", + "Songs": [ + { + "Number": 1, + "Title": "Whole Lotta Super Cats", + "Released": "21 May 2019", + "Genre": "*", + "Album": "Once bitten" + }, + { + "Number": 2, + "Title": "Enter Becky", + "Released": "16 Jan 2020", + "Genre": "*", + "Album": "Once bitten" + }, + { + "Number": 3, + "Title": "Your Cheatin' Flamingo", + "Released": "14 Jan 2020", + "Genre": "*", + "Album": "Once bitten" + }, + { + "Number": 4, + "Title": "Mad to Kiss", + "Released": "6 Nov 2019", + "Genre": "Synth-pop R&B", + "Album": "Once bitten" + }, + { + "Number": 5, + "Title": "Hotel Prague", + "Released": "20 Oct 2019", + "Genre": "ethno-tunes", + "Album": "Once bitten" + }, + { + "Number": 6, + "Title": "Jail on My Mind", + "Released": "31 May 2019", + "Genre": "Crunk reggaeton", + "Album": "Once bitten" + }, + { + "Number": 7, + "Title": "Amazing Blues", + "Released": "29 May 2019", + "Genre": "mystical parody-bap ", + "Album": "Once bitten" + }, + { + "Number": 8, + "Title": "Goody Two Iron Filings", + "Released": "4 Jul 2019", + "Genre": "Electro house Electropop", + "Album": "Once bitten" + }, + { + "Number": 9, + "Title": "I Love in Your Arms", + "Released": "7 Jun 2019", + "Genre": "R&B", + "Album": "Once bitten" + }, + { + "Number": 10, + "Title": "Truly Madly Amazing", + "Released": "12 Sep 2019", + "Genre": "ethno-tunes", + "Album": "Once bitten" + } + ] + }, + { + "Album": "Your graciousness", + "LaunchDate": "November 17, 2004", + "BillboardReview": 69, + "USBillboard200": 30, + "Artist": "Izabella Tabakova", + "Songs": [ + { + "Number": 1, + "Title": "We Shall Tickle", + "Released": "31 Aug 2019", + "Genre": "old emo-garage ", + "Album": "Your graciousness" + }, + { + "Number": 2, + "Title": "Snail Boogie", + "Released": "14 Jun 2019", + "Genre": "*", + "Album": "Your graciousness" + }, + { + "Number": 3, + "Title": "Amazing Liz", + "Released": "15 Oct 2019", + "Genre": "*", + "Album": "Your graciousness" + }, + { + "Number": 4, + "Title": "When Sexy Aardvarks Cry", + "Released": "1 Oct 2019", + "Genre": "whimsical comedy-grass ", + "Album": "Your graciousness" + }, + { + "Number": 5, + "Title": "Stand By Dave", + "Released": "18 Aug 2019", + "Genre": "unblack electronic-trip-hop", + "Album": "Your graciousness" + }, + { + "Number": 6, + "Title": "The Golf Course is Your Land", + "Released": "2 Apr 2019", + "Genre": "*", + "Album": "Your graciousness" + }, + { + "Number": 7, + "Title": "Where Have All the Men Gone?", + "Released": "29 Apr 2019", + "Genre": "*", + "Album": "Your graciousness" + }, + { + "Number": 8, + "Title": "Rhythm of the Leg", + "Released": "5 Aug 2019", + "Genre": "ethno-tunes", + "Album": "Your graciousness" + }, + { + "Number": 9, + "Title": "Baby, I Need Your Hats", + "Released": "5 Dec 2019", + "Genre": "neuro-tunes", + "Album": "Your graciousness" + }, + { + "Number": 10, + "Title": "Stand by Your Cat", + "Released": "25 Jul 2019", + "Genre": "*", + "Album": "Your graciousness" + } + ] + }, + { + "Album": "Dark matters", + "LaunchDate": "November 3, 2002", + "BillboardReview": 79, + "USBillboard200": 85, + "Artist": "Izabella Tabakova", + "Songs": [ + { + "Number": 1, + "Title": "Hiding in the Light", + "Released": "24 Jan 2019", + "Genre": "Synth-pop R&B", + "Album": "Dark matters" + }, + { + "Number": 2, + "Title": "Furious River", + "Released": "13 Jan 2020", + "Genre": "Electro house Electropop", + "Album": "Dark matters" + }, + { + "Number": 3, + "Title": "Wild Crying", + "Released": "27 Feb 2019", + "Genre": "Electro house Electropop", + "Album": "Dark matters" + }, + { + "Number": 4, + "Title": "Light of Dream", + "Released": "01 Jun 2019", + "Genre": "Crunk reggaeton", + "Album": "Dark matters" + }, + { + "Number": 5, + "Title": "Light of Dream", + "Released": "24 Aug 2019", + "Genre": "*", + "Album": "Dark matters" + }, + { + "Number": 6, + "Title": "Storm of Light", + "Released": "26 Feb 2019", + "Genre": "*", + "Album": "Dark matters" + }, + { + "Number": 7, + "Title": "Dark Storm", + "Released": "18 Jan 2020", + "Genre": "R&B", + "Album": "Dark matters" + }, + { + "Number": 8, + "Title": "Dark Calling", + "Released": "20 Mar 2019", + "Genre": "Crunk reggaeton", + "Album": "Dark matters" + }, + { + "Number": 9, + "Title": "Sky of Whisper", + "Released": "30 Jan 2019", + "Genre": "ethno-tunes", + "Album": "Dark matters" + }, + { + "Number": 10, + "Title": "Dancing in the Light", + "Released": "28 Nov 2019", + "Genre": "Synth-pop R&B", + "Album": "Dark matters" + } + ] + } + ] + }, + { + "ID": 6, + "Artist": "Nguyễn Diệp Chi", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/nguyen.jpg", + "Debut": 1992, + "GrammyNominations": 4, + "GrammyAwards": 2, + "HasGrammyAward": true, + "Tours": [ + + ], + "Albums": [ + { + "Album": "Library of liberty", + "LaunchDate": "December 22, 2003", + "BillboardReview": 93, + "USBillboard200": 5, + "Artist": "Nguyễn Diệp Chi", + "Songs": [ + { + "Number": 1, + "Title": "Echo of River", + "Released": "05 Mar 2019", + "Genre": "Synth-pop R&B", + "Album": "Library of liberty" + }, + { + "Number": 2, + "Title": "Heart of River", + "Released": "12 Jan 2020", + "Genre": "Electro house Electropop", + "Album": "Library of liberty" + }, + { + "Number": 3, + "Title": "Dark Light", + "Released": "09 Aug 2019", + "Genre": "Electro house Electropop", + "Album": "Library of liberty" + }, + { + "Number": 4, + "Title": "Dark Fire", + "Released": "22 Jun 2019", + "Genre": "R&B", + "Album": "Library of liberty" + }, + { + "Number": 5, + "Title": "Flying in the Fire", + "Released": "22 Jul 2019", + "Genre": "*", + "Album": "Library of liberty" + }, + { + "Number": 6, + "Title": "Shadow of Heart", + "Released": "02 Jan 2020", + "Genre": "*", + "Album": "Library of liberty" + }, + { + "Number": 7, + "Title": "Fire of Fire", + "Released": "27 Jan 2019", + "Genre": "*", + "Album": "Library of liberty" + }, + { + "Number": 8, + "Title": "Falling in the River", + "Released": "05 Aug 2019", + "Genre": "Crunk reggaeton", + "Album": "Library of liberty" + }, + { + "Number": 9, + "Title": "Fire of Light", + "Released": "31 Dec 2019", + "Genre": "ethno-tunes", + "Album": "Library of liberty" + }, + { + "Number": 10, + "Title": "Bright Flying", + "Released": "24 Jan 2019", + "Genre": "*", + "Album": "Library of liberty" + } + ] + } + ] + }, + { + "ID": 7, + "Artist": "Eva Lee", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/eva.jpg", + "Debut": 2008, + "GrammyNominations": 2, + "GrammyAwards": 0, + "HasGrammyAward": false, + "Tours": [ + + ], + "Albums": [ + { + "Album": "Just a tease", + "LaunchDate": "May 3, 2001", + "BillboardReview": 91, + "USBillboard200": 29, + "Artist": "Eva Lee", + "Songs": [ + { + "Number": 1, + "Title": "Dancing in the Shadow", + "Released": "02 Aug 2019", + "Genre": "ethno-tunes", + "Album": "Just a tease" + }, + { + "Number": 2, + "Title": "Silent Whisper", + "Released": "09 Jul 2019", + "Genre": "*", + "Album": "Just a tease" + }, + { + "Number": 3, + "Title": "Crying in the Whisper", + "Released": "30 May 2019", + "Genre": "Crunk reggaeton", + "Album": "Just a tease" + }, + { + "Number": 4, + "Title": "River of Light", + "Released": "10 Jan 2019", + "Genre": "Electro house Electropop", + "Album": "Just a tease" + }, + { + "Number": 5, + "Title": "Golden River", + "Released": "15 Nov 2019", + "Genre": "*", + "Album": "Just a tease" + }, + { + "Number": 6, + "Title": "Burning in the Shadow", + "Released": "18 Apr 2019", + "Genre": "Crunk reggaeton", + "Album": "Just a tease" + }, + { + "Number": 7, + "Title": "Shadow of Sky", + "Released": "06 Sep 2019", + "Genre": "Crunk reggaeton", + "Album": "Just a tease" + }, + { + "Number": 8, + "Title": "Gentle Waiting", + "Released": "05 Dec 2019", + "Genre": "R&B", + "Album": "Just a tease" + }, + { + "Number": 9, + "Title": "Bright River", + "Released": "27 Jan 2020", + "Genre": "R&B", + "Album": "Just a tease" + }, + { + "Number": 10, + "Title": "Heart of Storm", + "Released": "07 Jan 2019", + "Genre": "Synth-pop R&B", + "Album": "Just a tease" + } + ] + } + ] + }, + { + "ID": 8, + "Artist": "Siri Jakobsson", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/siri.jpg", + "Debut": 1990, + "GrammyNominations": 2, + "GrammyAwards": 8, + "HasGrammyAward": true, + "Tours": [ + { + "Tour": "Basket case", + "StartedOn": "Jan 07", + "Location": "Europe, Asia", + "Headliner": "NO", + "TouredBy": "Siri Jakobsson" + }, + { + "Tour": "The bigger fish", + "StartedOn": "Dec 07", + "Location": "United States, Europe", + "Headliner": "YES", + "TouredBy": "Siri Jakobsson" + }, + { + "Tour": "Missed the boat", + "StartedOn": "Jun 09", + "Location": "Europe, Asia", + "Headliner": "NO", + "TouredBy": "Siri Jakobsson" + }, + { + "Tour": "Equivalent exchange", + "StartedOn": "Feb 06", + "Location": "United States, Europe", + "Headliner": "YES", + "TouredBy": "Siri Jakobsson" + }, + { + "Tour": "Damage control", + "StartedOn": "Oct 11", + "Location": "Australia, United States", + "Headliner": "NO", + "TouredBy": "Siri Jakobsson" + } + ], + "Albums": [ + { + "Album": "Under the bus", + "LaunchDate": "May 14, 2000", + "BillboardReview": 67, + "USBillboard200": 67, + "Artist": "Siri Jakobsson", + "Songs": [ + { + "Number": 1, + "Title": "Jack Broke My Heart At Tesco's", + "Released": "19 Jan 2020", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 2, + "Title": "Cat Deep, Hats High", + "Released": "5 Dec 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 3, + "Title": "In Snail We Trust", + "Released": "31 May 2019", + "Genre": "hardcore opera", + "Album": "Under the bus" + }, + { + "Number": 4, + "Title": "Liz's Waiting", + "Released": "22 Jul 2019", + "Genre": "emotional C-jam ", + "Album": "Under the bus" + }, + { + "Number": 5, + "Title": "Lifeless Blues", + "Released": "14 Jun 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 6, + "Title": "I Spin", + "Released": "26 Mar 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 7, + "Title": "Ring of Rock", + "Released": "12 Dec 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 8, + "Title": "Livin' on a Rock", + "Released": "17 Apr 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 9, + "Title": "Your Lifeless Heart", + "Released": "15 Sep 2019", + "Genre": "adult calypso-industrial", + "Album": "Under the bus" + }, + { + "Number": 10, + "Title": "The High Street on My Mind", + "Released": "11 Nov 2019", + "Genre": "calypso and mariachi", + "Album": "Under the bus" + }, + { + "Number": 11, + "Title": "Behind Ugly Curtains", + "Released": "8 May 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 12, + "Title": "Where Have All the Curtains Gone?", + "Released": "28 Jun 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 13, + "Title": "Ghost in My Apple", + "Released": "14 Dec 2019", + "Genre": "*", + "Album": "Under the bus" + }, + { + "Number": 14, + "Title": "I Chatter", + "Released": "30 Nov 2019", + "Genre": "*", + "Album": "Under the bus" + } + ] + } + ] + }, + { + "ID": 9, + "Artist": "Pablo Cambeiro", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/pablo.jpg", + "Debut": 2011, + "GrammyNominations": 5, + "GrammyAwards": 0, + "HasGrammyAward": false, + "Tours": [ + { + "Tour": "Beads", + "StartedOn": "May 11", + "Location": "Worldwide", + "Headliner": "NO", + "TouredBy": "Pablo Cambeiro" + }, + { + "Tour": "Concept art", + "StartedOn": "Dec 18", + "Location": "United States", + "Headliner": "YES", + "TouredBy": "Pablo Cambeiro" + }, + { + "Tour": "Glass shoe", + "StartedOn": "Jan 20", + "Location": "Worldwide", + "Headliner": "YES", + "TouredBy": "Pablo Cambeiro" + }, + { + "Tour": "Pushing buttons", + "StartedOn": "Feb 15", + "Location": "Europe, Asia", + "Headliner": "NO", + "TouredBy": "Pablo Cambeiro" + }, + { + "Tour": "Dark matters", + "StartedOn": "Jan 04", + "Location": "Australia, United States", + "Headliner": "YES", + "TouredBy": "Pablo Cambeiro" + }, + { + "Tour": "Greener grass", + "StartedOn": "Sep 09", + "Location": "United States, Europe", + "Headliner": "NO", + "TouredBy": "Pablo Cambeiro" + }, + { + "Tour": "Apparatus", + "StartedOn": "Nov 16", + "Location": "Europe", + "Headliner": "NO", + "TouredBy": "Pablo Cambeiro" + } + ], + "Albums": [ + { + "Album": "Fluke", + "LaunchDate": "August 4, 2017", + "BillboardReview": 93, + "USBillboard200": 98, + "Artist": "Pablo Cambeiro", + "Songs": [ + { + "Number": 1, + "Title": "Dancing in the Echo", + "Released": "03 Oct 2019", + "Genre": "Crunk reggaeton", + "Album": "Fluke" + }, + { + "Number": 2, + "Title": "Dream of Dream", + "Released": "03 Mar 2019", + "Genre": "*", + "Album": "Fluke" + }, + { + "Number": 3, + "Title": "Calling in the Echo", + "Released": "16 Sep 2019", + "Genre": "*", + "Album": "Fluke" + }, + { + "Number": 4, + "Title": "Light of Light", + "Released": "25 May 2019", + "Genre": "Electro house Electropop", + "Album": "Fluke" + }, + { + "Number": 5, + "Title": "Bright Light", + "Released": "21 Mar 2019", + "Genre": "R&B", + "Album": "Fluke" + }, + { + "Number": 6, + "Title": "Storm of Echo", + "Released": "17 Jul 2019", + "Genre": "Synth-pop R&B", + "Album": "Fluke" + }, + { + "Number": 7, + "Title": "Lonely Calling", + "Released": "10 Apr 2019", + "Genre": "ethno-tunes", + "Album": "Fluke" + }, + { + "Number": 8, + "Title": "Gentle Falling", + "Released": "28 Nov 2019", + "Genre": "Synth-pop R&B", + "Album": "Fluke" + }, + { + "Number": 9, + "Title": "Wild Flying", + "Released": "26 Nov 2019", + "Genre": "Crunk reggaeton", + "Album": "Fluke" + }, + { + "Number": 10, + "Title": "Sky of Dream", + "Released": "29 May 2019", + "Genre": "R&B", + "Album": "Fluke" + } + ] + }, + { + "Album": "Crowd control", + "LaunchDate": "August 26, 2003", + "BillboardReview": 68, + "USBillboard200": 84, + "Artist": "Pablo Cambeiro", + "Songs": [ + { + "Number": 1, + "Title": "My Bed on My Mind", + "Released": "25 Mar 2019", + "Genre": "ethno-tunes", + "Album": "Crowd control" + }, + { + "Number": 2, + "Title": "Bright Blues", + "Released": "28 Sep 2019", + "Genre": "neuro-tunes", + "Album": "Crowd control" + }, + { + "Number": 3, + "Title": "Sail, Sail, Sail!", + "Released": "5 Mar 2019", + "Genre": "*", + "Album": "Crowd control" + }, + { + "Number": 4, + "Title": "Hotel My Bed", + "Released": "22 Mar 2019", + "Genre": "*", + "Album": "Crowd control" + }, + { + "Number": 5, + "Title": "Gonna Make You Mash", + "Released": "18 May 2019", + "Genre": "*", + "Album": "Crowd control" + }, + { + "Number": 6, + "Title": "Straight Outta America", + "Released": "16 Jan 2020", + "Genre": "hardcore opera", + "Album": "Crowd control" + }, + { + "Number": 7, + "Title": "I Drive", + "Released": "23 Feb 2019", + "Genre": "emotional C-jam ", + "Album": "Crowd control" + }, + { + "Number": 8, + "Title": "Like a Teddy", + "Released": "31 Aug 2019", + "Genre": "*", + "Album": "Crowd control" + }, + { + "Number": 9, + "Title": "Teddy Boogie", + "Released": "30 Nov 2019", + "Genre": "*", + "Album": "Crowd control" + } + ] + } + ] + }, + { + "ID": 10, + "Artist": "Athar Malakooti", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/athar.jpg", + "Debut": 2017, + "GrammyNominations": 0, + "GrammyAwards": 0, + "HasGrammyAward": false, + "Tours": [ + + ], + "Albums": [ + { + "Album": "Pushing up daisies", + "LaunchDate": "February 24, 2016", + "BillboardReview": 74, + "USBillboard200": 77, + "Artist": "Athar Malakooti", + "Songs": [ + { + "Number": 1, + "Title": "Hiding in the Whisper", + "Released": "03 Apr 2019", + "Genre": "R&B", + "Album": "Pushing up daisies" + }, + { + "Number": 2, + "Title": "Wicked Light", + "Released": "21 Aug 2019", + "Genre": "R&B", + "Album": "Pushing up daisies" + }, + { + "Number": 3, + "Title": "Flying in the River", + "Released": "03 Feb 2020", + "Genre": "Synth-pop R&B", + "Album": "Pushing up daisies" + }, + { + "Number": 4, + "Title": "Wicked Hiding", + "Released": "15 Sep 2019", + "Genre": "Synth-pop R&B", + "Album": "Pushing up daisies" + }, + { + "Number": 5, + "Title": "Lonely Light", + "Released": "13 May 2019", + "Genre": "Electro house Electropop", + "Album": "Pushing up daisies" + }, + { + "Number": 6, + "Title": "Bright Dancing", + "Released": "10 Apr 2019", + "Genre": "Synth-pop R&B", + "Album": "Pushing up daisies" + }, + { + "Number": 7, + "Title": "Gentle Dream", + "Released": "21 May 2019", + "Genre": "*", + "Album": "Pushing up daisies" + }, + { + "Number": 8, + "Title": "Sky of Echo", + "Released": "09 Jun 2019", + "Genre": "Synth-pop R&B", + "Album": "Pushing up daisies" + }, + { + "Number": 9, + "Title": "Breaking in the Sky", + "Released": "27 Dec 2019", + "Genre": "Crunk reggaeton", + "Album": "Pushing up daisies" + }, + { + "Number": 10, + "Title": "Whisper of Shadow", + "Released": "04 Jan 2019", + "Genre": "ethno-tunes", + "Album": "Pushing up daisies" + } + ] + } + ] + }, + { + "ID": 11, + "Artist": "Marti Valencia", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/marti.jpg", + "Debut": 2004, + "GrammyNominations": 1, + "GrammyAwards": 1, + "HasGrammyAward": true, + "Tours": [ + { + "Tour": "Cat eat cat world", + "StartedOn": "Sep 00", + "Location": "Worldwide", + "Headliner": "YES", + "TouredBy": "Marti Valencia" + }, + { + "Tour": "Final straw", + "StartedOn": "Sep 06", + "Location": "United States, Europe", + "Headliner": "NO", + "TouredBy": "Marti Valencia" + } + ], + "Albums": [ + { + "Album": "Nemesis", + "LaunchDate": "June 30, 2004", + "BillboardReview": 94, + "USBillboard200": 9, + "Artist": "Marti Valencia", + "Songs": [ + { + "Number": 1, + "Title": "Hiding in the Sky", + "Released": "26 Nov 2019", + "Genre": "Synth-pop R&B", + "Album": "Nemesis" + }, + { + "Number": 2, + "Title": "Waiting in the Echo", + "Released": "10 Jul 2019", + "Genre": "ethno-tunes", + "Album": "Nemesis" + }, + { + "Number": 3, + "Title": "Wicked Shadow", + "Released": "29 Jul 2019", + "Genre": "Synth-pop R&B", + "Album": "Nemesis" + }, + { + "Number": 4, + "Title": "Crying in the Whisper", + "Released": "09 Apr 2019", + "Genre": "*", + "Album": "Nemesis" + }, + { + "Number": 5, + "Title": "Echo of Storm", + "Released": "19 Nov 2019", + "Genre": "Crunk reggaeton", + "Album": "Nemesis" + }, + { + "Number": 6, + "Title": "Shadow of Sky", + "Released": "24 Jul 2019", + "Genre": "Crunk reggaeton", + "Album": "Nemesis" + }, + { + "Number": 7, + "Title": "Golden Hiding", + "Released": "12 Dec 2019", + "Genre": "Electro house Electropop", + "Album": "Nemesis" + }, + { + "Number": 8, + "Title": "Wild Dancing", + "Released": "17 Aug 2019", + "Genre": "Synth-pop R&B", + "Album": "Nemesis" + }, + { + "Number": 9, + "Title": "Bright Burning", + "Released": "30 Aug 2019", + "Genre": "Electro house Electropop", + "Album": "Nemesis" + }, + { + "Number": 10, + "Title": "Flying in the River", + "Released": "02 Sep 2019", + "Genre": "*", + "Album": "Nemesis" + } + ] + }, + { + "Album": "First chance", + "LaunchDate": "January 7, 2019", + "BillboardReview": 96, + "USBillboard200": 19, + "Artist": "Marti Valencia", + "Songs": [ + { + "Number": 1, + "Title": "My Name is Jason", + "Released": "12 Jul 2019", + "Genre": "*", + "Album": "First chance" + }, + { + "Number": 2, + "Title": "Amazing Andy", + "Released": "5 Mar 2019", + "Genre": "*", + "Album": "First chance" + }, + { + "Number": 3, + "Title": "The Number of your Knight", + "Released": "4 Dec 2019", + "Genre": "*", + "Album": "First chance" + }, + { + "Number": 4, + "Title": "I Sail", + "Released": "3 Mar 2019", + "Genre": "*", + "Album": "First chance" + }, + { + "Number": 5, + "Title": "Goody Two Hands", + "Released": "11 Oct 2019", + "Genre": "Electro house Electropop", + "Album": "First chance" + }, + { + "Number": 6, + "Title": "Careful With That Knife", + "Released": "18 Dec 2019", + "Genre": "R&B", + "Album": "First chance" + }, + { + "Number": 7, + "Title": "Four Single Ants", + "Released": "18 Jan 2020", + "Genre": "*", + "Album": "First chance" + }, + { + "Number": 8, + "Title": "Kiss Forever", + "Released": "10 Aug 2019", + "Genre": "*", + "Album": "First chance" + }, + { + "Number": 9, + "Title": "Rich's Waiting", + "Released": "15 Mar 2019", + "Genre": "Synth-pop R&B", + "Album": "First chance" + }, + { + "Number": 10, + "Title": "Japan is Your Land", + "Released": "7 Mar 2019", + "Genre": "ethno-tunes", + "Album": "First chance" + }, + { + "Number": 11, + "Title": "Pencils in My Banana", + "Released": "21 Jun 2019", + "Genre": "Crunk reggaeton", + "Album": "First chance" + }, + { + "Number": 12, + "Title": "I Sail in Your Arms", + "Released": "30 Apr 2019", + "Genre": "Synth-pop R&B", + "Album": "First chance" + } + ] + }, + { + "Album": "God's advocate", + "LaunchDate": "April 29, 2007", + "BillboardReview": 66, + "USBillboard200": 37, + "Artist": "Marti Valencia", + "Songs": [ + { + "Number": 1, + "Title": "Wild River", + "Released": "11 Jan 2019", + "Genre": "*", + "Album": "God's advocate" + }, + { + "Number": 2, + "Title": "Wicked Whisper", + "Released": "16 Feb 2019", + "Genre": "Electro house Electropop", + "Album": "God's advocate" + }, + { + "Number": 3, + "Title": "Storm of Heart", + "Released": "11 Aug 2019", + "Genre": "*", + "Album": "God's advocate" + }, + { + "Number": 4, + "Title": "Golden Dancing", + "Released": "02 Mar 2019", + "Genre": "Crunk reggaeton", + "Album": "God's advocate" + }, + { + "Number": 5, + "Title": "Calling in the Sky", + "Released": "10 Sep 2019", + "Genre": "Electro house Electropop", + "Album": "God's advocate" + }, + { + "Number": 6, + "Title": "Calling in the Heart", + "Released": "12 Jan 2019", + "Genre": "ethno-tunes", + "Album": "God's advocate" + }, + { + "Number": 7, + "Title": "Running in the Storm", + "Released": "10 Nov 2019", + "Genre": "Synth-pop R&B", + "Album": "God's advocate" + }, + { + "Number": 8, + "Title": "Wild Sky", + "Released": "10 Apr 2019", + "Genre": "R&B", + "Album": "God's advocate" + }, + { + "Number": 9, + "Title": "Crying in the Shadow", + "Released": "02 Mar 2019", + "Genre": "R&B", + "Album": "God's advocate" + }, + { + "Number": 10, + "Title": "Whisper of River", + "Released": "12 May 2019", + "Genre": "*", + "Album": "God's advocate" + } + ] + } + ] + }, + { + "ID": 12, + "Artist": "Alicia Stanger", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/alicia.jpg", + "Debut": 2010, + "GrammyNominations": 1, + "GrammyAwards": 0, + "HasGrammyAward": false, + "Tours": [ + + ], + "Albums": [ + { + "Album": "Forever alone", + "LaunchDate": "November 3, 2005", + "BillboardReview": 82, + "USBillboard200": 7, + "Artist": "Alicia Stanger", + "Songs": [ + { + "Number": 1, + "Title": "Shadow of Light", + "Released": "24 Mar 2019", + "Genre": "ethno-tunes", + "Album": "Forever alone" + }, + { + "Number": 2, + "Title": "Running in the Echo", + "Released": "03 May 2019", + "Genre": "Crunk reggaeton", + "Album": "Forever alone" + }, + { + "Number": 3, + "Title": "Gentle Dream", + "Released": "24 Aug 2019", + "Genre": "Crunk reggaeton", + "Album": "Forever alone" + }, + { + "Number": 4, + "Title": "Furious River", + "Released": "24 Apr 2019", + "Genre": "ethno-tunes", + "Album": "Forever alone" + }, + { + "Number": 5, + "Title": "Wild Whisper", + "Released": "09 Mar 2019", + "Genre": "ethno-tunes", + "Album": "Forever alone" + }, + { + "Number": 6, + "Title": "Whisper of Sky", + "Released": "24 Jul 2019", + "Genre": "Crunk reggaeton", + "Album": "Forever alone" + }, + { + "Number": 7, + "Title": "Lonely Storm", + "Released": "01 May 2019", + "Genre": "Crunk reggaeton", + "Album": "Forever alone" + }, + { + "Number": 8, + "Title": "Dancing in the River", + "Released": "17 Dec 2019", + "Genre": "*", + "Album": "Forever alone" + }, + { + "Number": 9, + "Title": "Electric Fire", + "Released": "17 Oct 2019", + "Genre": "Electro house Electropop", + "Album": "Forever alone" + }, + { + "Number": 10, + "Title": "Electric Sky", + "Released": "25 Sep 2019", + "Genre": "ethno-tunes", + "Album": "Forever alone" + } + ] + } + ] + }, + { + "ID": 13, + "Artist": "Peter Taylor", + "Photo": "https://static.infragistics.com/xplatform/images/people/names/peter.jpg", + "Debut": 2005, + "GrammyNominations": 0, + "GrammyAwards": 2, + "HasGrammyAward": true, + "Tours": [ + { + "Tour": "Love", + "StartedOn": "Jun 04", + "Location": "Europe, Asia", + "Headliner": "YES", + "TouredBy": "Peter Taylor" + }, + { + "Tour": "Fault of treasures", + "StartedOn": "Oct 13", + "Location": "North America", + "Headliner": "NO", + "TouredBy": "Peter Taylor" + }, + { + "Tour": "For eternity", + "StartedOn": "Mar 05", + "Location": "United States", + "Headliner": "YES", + "TouredBy": "Peter Taylor" + }, + { + "Tour": "Time flies", + "StartedOn": "Jun 03", + "Location": "North America", + "Headliner": "NO", + "TouredBy": "Peter Taylor" + }, + { + "Tour": "Highest difficulty", + "StartedOn": "Nov 01", + "Location": "Worldwide", + "Headliner": "YES", + "TouredBy": "Peter Taylor" + }, + { + "Tour": "Sleeping dogs", + "StartedOn": "May 04", + "Location": "United States, Europe", + "Headliner": "NO", + "TouredBy": "Peter Taylor" + } + ], + "Albums": [ + { + "Album": "Decisions decisions", + "LaunchDate": "April 10, 2008", + "BillboardReview": 85, + "USBillboard200": 35, + "Artist": "Peter Taylor", + "Songs": [ + { + "Number": 1, + "Title": "Calling in the Dream", + "Released": "01 Aug 2019", + "Genre": "R&B", + "Album": "Decisions decisions" + }, + { + "Number": 2, + "Title": "Electric Burning", + "Released": "10 Sep 2019", + "Genre": "Electro house Electropop", + "Album": "Decisions decisions" + }, + { + "Number": 3, + "Title": "Dark Flying", + "Released": "28 Apr 2019", + "Genre": "*", + "Album": "Decisions decisions" + }, + { + "Number": 4, + "Title": "Gentle Sky", + "Released": "20 Nov 2019", + "Genre": "ethno-tunes", + "Album": "Decisions decisions" + }, + { + "Number": 5, + "Title": "Gentle Calling", + "Released": "13 Jan 2019", + "Genre": "Crunk reggaeton", + "Album": "Decisions decisions" + }, + { + "Number": 6, + "Title": "Golden Falling", + "Released": "14 Feb 2019", + "Genre": "Crunk reggaeton", + "Album": "Decisions decisions" + }, + { + "Number": 7, + "Title": "Silent River", + "Released": "13 Feb 2019", + "Genre": "R&B", + "Album": "Decisions decisions" + }, + { + "Number": 8, + "Title": "Furious Calling", + "Released": "11 Jun 2019", + "Genre": "Synth-pop R&B", + "Album": "Decisions decisions" + }, + { + "Number": 9, + "Title": "Running in the Echo", + "Released": "06 Nov 2019", + "Genre": "Electro house Electropop", + "Album": "Decisions decisions" + }, + { + "Number": 10, + "Title": "Furious River", + "Released": "12 Mar 2019", + "Genre": "*", + "Album": "Decisions decisions" + } + ] + }, + { + "Album": "Climate changed", + "LaunchDate": "June 20, 2015", + "BillboardReview": 66, + "USBillboard200": 89, + "Artist": "Peter Taylor", + "Songs": [ + { + "Number": 1, + "Title": "Dark Crying", + "Released": "27 Apr 2019", + "Genre": "Electro house Electropop", + "Album": "Climate changed" + }, + { + "Number": 2, + "Title": "Dark Waiting", + "Released": "14 Nov 2019", + "Genre": "Synth-pop R&B", + "Album": "Climate changed" + }, + { + "Number": 3, + "Title": "Furious Waiting", + "Released": "23 May 2019", + "Genre": "*", + "Album": "Climate changed" + }, + { + "Number": 4, + "Title": "Running in the Echo", + "Released": "29 Nov 2019", + "Genre": "Crunk reggaeton", + "Album": "Climate changed" + }, + { + "Number": 5, + "Title": "Dream of Sky", + "Released": "31 Oct 2019", + "Genre": "Crunk reggaeton", + "Album": "Climate changed" + }, + { + "Number": 6, + "Title": "Hiding in the Heart", + "Released": "09 Aug 2019", + "Genre": "R&B", + "Album": "Climate changed" + }, + { + "Number": 7, + "Title": "Sky of Storm", + "Released": "01 Jun 2019", + "Genre": "R&B", + "Album": "Climate changed" + }, + { + "Number": 8, + "Title": "Light of Storm", + "Released": "17 Jan 2020", + "Genre": "ethno-tunes", + "Album": "Climate changed" + }, + { + "Number": 9, + "Title": "Light of Sky", + "Released": "26 May 2019", + "Genre": "*", + "Album": "Climate changed" + }, + { + "Number": 10, + "Title": "Golden River", + "Released": "19 Jun 2019", + "Genre": "*", + "Album": "Climate changed" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.css b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.css new file mode 100644 index 0000000000..3b330b391d --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.css @@ -0,0 +1,7 @@ +/* shared styles are loaded from: */ +/* https://static.infragistics.com/xplatform/css/samples */ + + #grid { + --ig-size: var(--ig-size-medium); + } + diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx new file mode 100644 index 0000000000..943fa23c23 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx @@ -0,0 +1,304 @@ + +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import { + IgrHierarchicalGrid, + IgrHierarchicalGridModule, + IgrColumn, + IgrRowIsland, + IgrPaginator, +} from 'igniteui-react-grids'; +import { IgrCheckbox } from 'igniteui-react'; +import { IgrList, IgrListItem, IgrListHeader } from 'igniteui-react'; +import SingersData from './SingersData.json'; + +import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; + +type Singer = { + ID: number; + Artist: string; + Photo: string; + Debut: number; + GrammyNominations: number; + GrammyAwards: number; + HasGrammyAward: boolean; + Tours?: any[]; +}; + +enum GridSection { + THEAD = 'thead', + TBODY = 'tbody', + FOOTER = 'tfoot', +} + +enum ItemAction { + Filterable, + Sortable, + Selectable, + Groupable, + Collapsible, + Expandable, + Editable, + Always, +} + +type Item = { + title: string; + subTitle: string; + action: ItemAction; + active: boolean; + completed: boolean; +}; + +const makeItem = ( + title: string, + subTitle: string, + completed: boolean, + action: ItemAction +): Item => ({ title, subTitle, completed, action, active: action === ItemAction.Always }); + +const theadKeyCombinations: Item[] = [ + makeItem('Space', 'select column', false, ItemAction.Selectable), + makeItem('Ctrl + ↑/↓', 'sort column asc/desc', false, ItemAction.Sortable), + makeItem('Shift + Alt + ←/→', 'group / ungroup column', false, ItemAction.Groupable), + makeItem('Alt + arrows', 'expand / collapse column group', false, ItemAction.Collapsible), + makeItem('Ctrl + Shift + L', 'open Excel-style filter', false, ItemAction.Filterable), + makeItem('Alt + L', 'open Advanced filtering', false, ItemAction.Filterable), +]; + +const tbodyKeyCombinations: Item[] = [ + makeItem('Enter', 'enter edit mode', false, ItemAction.Editable), + makeItem('Alt + ←/↑', 'collapse row detail', false, ItemAction.Collapsible), + makeItem('Alt + →/↓', 'expand row detail', false, ItemAction.Collapsible), + makeItem('Alt + ←/→', 'expand/collapse group row', false, ItemAction.Expandable), + makeItem('Ctrl + Home/End', 'jump to first/last cell', false, ItemAction.Always), +]; + +const summaryCombinations: Item[] = [ + makeItem('←', 'move left', false, ItemAction.Always), + makeItem('→', 'move right', false, ItemAction.Always), + makeItem('Home', 'first summary cell', false, ItemAction.Always), + makeItem('End', 'last summary cell', false, ItemAction.Always), +]; + +const cloneItems = (items: Item[]) => items.map(i => ({ ...i })); + +function enableActions(list: Item[], actions: ItemAction[]) { + return list.map(i => + i.action === ItemAction.Always + ? { ...i, active: true } + : { ...i, active: actions.includes(i.action) } + ); +} + +function markCompleted(list: Item[], idx: number, value: boolean) { + const next = list.slice(); + next[idx] = { ...next[idx], completed: value }; + return next; +} + +function checkEditMode(): boolean { + const editCells = document.querySelectorAll('.igx-grid__td--editing, .igx-grid__td--cell-editing'); + const editInputs = document.querySelectorAll('.igx-grid__td input, .igx-grid__td textarea, .igx-grid__td select'); + const editClasses = document.querySelector('.igx-grid--editing'); + return editCells.length > 0 || editInputs.length > 0 || !!editClasses; +} + +export default function HierarchicalGridKeyboardNavGuide() { + const gridRef = useRef(null); + + const data = SingersData as Singer[]; + const [gridSection, setGridSection] = useState(GridSection.THEAD); + const [items, setItems] = useState(cloneItems(theadKeyCombinations)); + + const headerText = useMemo(() => { + if (gridSection === GridSection.THEAD) return 'HEADER COMBINATIONS'; + if (gridSection === GridSection.TBODY) return 'BODY COMBINATIONS'; + if (gridSection === GridSection.FOOTER) return 'SUMMARY COMBINATIONS'; + return ''; + }, [gridSection]); + + useEffect(() => { + const handleGlobalKeyDown = (evt: KeyboardEvent) => { + const target = evt.target as HTMLElement; + const gridElement = (gridRef.current as any)?.nativeElement || gridRef.current; + const isGridFocused = gridElement?.contains?.(target); + + if (!isGridFocused || checkEditMode()) { + return; + } + + const key = evt.key?.toLowerCase(); + const { altKey, ctrlKey, shiftKey } = evt; + + if (altKey && (key === 'arrowleft' || key === 'arrowright' || key === 'arrowup' || key === 'arrowdown')) { + if (gridSection === GridSection.TBODY) { + if (key === 'arrowleft' || key === 'arrowup') { + setItems((prev: Item[]) => markCompleted(prev, 1, true)); + } else if (key === 'arrowright' || key === 'arrowdown') { + setItems((prev: Item[]) => markCompleted(prev, 2, true)); + } + } + } + + if (ctrlKey && (key === 'home' || key === 'end')) { + if (gridSection === GridSection.TBODY) { + setItems((prev: Item[]) => markCompleted(prev, 4, true)); + } + } + + if (key === 'enter' && gridSection === GridSection.TBODY) { + setTimeout(() => { + if (checkEditMode()) { + setItems((prev: Item[]) => markCompleted(prev, 0, true)); + } + }, 100); + } + }; + + document.addEventListener('keydown', handleGlobalKeyDown, true); + return () => { + document.removeEventListener('keydown', handleGlobalKeyDown, true); + }; + }, [gridSection]); + + const updateSectionFromActiveNode = () => { + const grid = gridRef.current; + const active = (grid as any)?.navigation?.activeNode; + const rows = grid?.data?.length ?? data.length; + + if (active && typeof active.row === 'number') { + if (active.row < 0) { + setGridSection(GridSection.THEAD); + setItems(cloneItems(theadKeyCombinations)); + } else if (active.row >= rows) { + setGridSection(GridSection.FOOTER); + setItems(cloneItems(summaryCombinations)); + } else { + setGridSection(GridSection.TBODY); + setItems(cloneItems(tbodyKeyCombinations)); + } + } + }; + + const refreshHeaderActions = () => { + const grid: any = gridRef.current; + if (!grid) return; + + if (gridSection !== GridSection.THEAD) return; + + const active = grid?.navigation?.activeNode; + const col = grid.visibleColumns?.find( + (c: any) => c.visibleIndex === active?.column && c.level === active?.level + ); + + const actions: ItemAction[] = []; + if (col?.sortable) actions.push(ItemAction.Sortable); + if (col?.filterable && !col?.columnGroup) actions.push(ItemAction.Filterable); + if (col?.collapsible) actions.push(ItemAction.Collapsible); + if (col?.groupable) actions.push(ItemAction.Groupable); + if (col?.selectable) actions.push(ItemAction.Selectable); + if (col?.editable) actions.push(ItemAction.Editable); + + setItems((prev: Item[]) => enableActions(prev, actions)); + }; + + const onActiveNodeChange = () => { + updateSectionFromActiveNode(); + refreshHeaderActions(); + }; + + const onRowToggle = () => { + if (gridSection === GridSection.TBODY) { + setItems((prev: Item[]) => markCompleted(prev, 3, true)); + } + }; + + const onColumnSelectionChanging = (e: any) => { + if (e?.detail?.event?.type === 'keydown') { + setItems((prev: Item[]) => markCompleted(prev, 0, true)); + } + }; + + return ( +
+
+ + + + + + + + + + + + + + + + + +
+ +
+ + {items.length > 0 && ( + + {headerText} + + )} + {items.map((c: Item, idx: number) => ( + +
+
+

{c.title}

+

{c.subTitle}

+
+ { + const checked = !!e?.detail; + setItems((prev: Item[]) => markCompleted(prev, idx, checked)); + }} + /> +
+
+ ))} + + {items.length === 0 && ( +
+
Use the browser navigation until you reach one of the following grid sections:
+
    +
  • Header
  • +
  • Body
  • +
  • Summary
  • +
+
When reached, an action list will be shown.
+
+ )} +
+
+
+ ); +} + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render(); diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/react-app-env.d.ts b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/react-app-env.d.ts new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/tsconfig.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/tsconfig.json new file mode 100644 index 0000000000..8c0d146f95 --- /dev/null +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true, + "baseUrl": ".", + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "noUnusedLocals": false, + "importHelpers": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": false, + "isolatedModules": true, + "noEmit": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts", + "**/odatajs-4.0.0.js", + "config-overrides.js" + ], + "include": [ + "src" + ] +} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js new file mode 100644 index 0000000000..7168b71441 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js @@ -0,0 +1,78 @@ +// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project +module.exports = { + parser: "@typescript-eslint/parser", // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module", // Allows for the use of imports + ecmaFeatures: { + jsx: true // Allows for the parsing of JSX + } + }, + settings: { + react: { + version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use + } + }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react + "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-prototype-builtins": "off", + "no-mixed-spaces-and-tabs": 0, + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-var": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-mixed-spaces-and-tabs": 0, + "no-prototype-builtins": "off", + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } + } + ] + }; \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md new file mode 100644 index 0000000000..ed2f5e5fc6 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md @@ -0,0 +1,56 @@ + + + +This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## Branches + +> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository. + +## Instructions + +Follow these instructions to run this example: + + +``` +git clone https://github.com/IgniteUI/igniteui-react-examples.git +git checkout master +cd ./igniteui-react-examples +cd ./samples/grids/grid/keyboard-custom-navigation +``` + +open above folder in VS Code or type: +``` +code . +``` + +In terminal window, run: +``` +npm install --legacy-peer-deps +npm run-script start +``` + +Then open http://localhost:4200/ in your browser + + +## Learn More + +To learn more about **Ignite UI for React** components, check out the [React documentation](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html). diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json new file mode 100644 index 0000000000..9246bcd77a --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json @@ -0,0 +1,48 @@ +{ + "name": "example-ignite-ui-react", + "description": "This project provides example of using Ignite UI for React components", + "author": "Infragistics", + "version": "1.4.0", + "license": "", + "homepage": ".", + "private": true, + "scripts": { + "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start", + "build": "react-scripts --max_old_space_size=10240 build ", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject", + "lint": "eslint ./src/**/*.{ts,tsx}" + }, + "dependencies": { + "igniteui-dockmanager": "^1.17.0", + "igniteui-react": "^19.2.0", + "igniteui-react-core": "^19.0.1", + "igniteui-react-grids": "^19.2.0", + "igniteui-react-inputs": "^19.0.1", + "igniteui-react-layouts": "^19.0.1", + "igniteui-webcomponents": "^6.2.0", + "lit-html": "^3.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "^5.0.1", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@types/jest": "^29.2.0", + "@types/node": "^18.11.7", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "eslint": "^8.33.0", + "eslint-config-react": "^1.1.7", + "eslint-plugin-react": "^7.20.0", + "react-app-rewired": "^2.2.1", + "typescript": "^4.8.4", + "worker-loader": "^3.0.8" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html new file mode 100644 index 0000000000..e2d3265576 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html @@ -0,0 +1,11 @@ + + + + Sample | Ignite UI | React | infragistics + + + + +
+ + \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json new file mode 100644 index 0000000000..07f53508eb --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json @@ -0,0 +1,5 @@ +{ + "infiniteLoopProtection": false, + "hardReloadOnChange": false, + "view": "browser" +} \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts new file mode 100644 index 0000000000..0728ee874d --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts @@ -0,0 +1,75 @@ +export class EmployeesNestedDataItem { + public constructor(init: Partial) { + Object.assign(this, init); + } + + public ID: number; + public Age: number; + public Salary: number; + public Productivity: number; + public City: string; + public Country: string; + public Phone: string; + public HireDate: string; + public Name: string; + public Title: string; + public Employees: EmployeesNestedDataItem_EmployeesItem[]; + +} +export class EmployeesNestedDataItem_EmployeesItem { + public constructor(init: Partial) { + Object.assign(this, init); + } + + public Age: number; + public Salary: number; + public Productivity: number; + public City: string; + public Country: string; + public Phone: string; + public HireDate: string; + public ID: number; + public Name: string; + public Title: string; + +} +export class EmployeesNestedData extends Array { + public constructor(items: Array | number = -1) { + if (Array.isArray(items)) { + super(...items); + } else { + const newItems = [ + new EmployeesNestedDataItem({ ID: 1, Age: 55, Salary: 80000, Productivity: 90, City: `Berlin`, Country: `Germany`, Phone: `609-202-505`, HireDate: `2008-03-20`, Name: `John Winchester`, Title: `Development Manager`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] + }), + new EmployeesNestedDataItem({ ID: 4, Age: 42, Salary: 90000, Productivity: 80, City: `Kielce`, Country: `Poland`, Phone: `609-202-505`, HireDate: `2014-01-22`, Name: `Ana Sanders`, Title: `CEO`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 44, Salary: 80000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-202-505`, HireDate: `2014-04-04`, ID: 14, Name: `Laurence Johnson`, Title: `Director` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 25, Salary: 85000, Productivity: 55, City: `Paris`, Country: `France`, Phone: `609-202-505`, HireDate: `2017-11-09`, ID: 5, Name: `Elizabeth Richards`, Title: `Vice President` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 39, Salary: 88000, Productivity: 88, City: `London`, Country: `UK`, Phone: `609-202-505`, HireDate: `2010-03-22`, ID: 13, Name: `Trevor Ashworth`, Title: `Director` })] + }), + new EmployeesNestedDataItem({ ID: 18, Age: 49, Salary: 77000, Productivity: 70, City: `Manchester`, Country: `UK`, Phone: `222-555-577`, HireDate: `2014-01-22`, Name: `Victoria Lincoln`, Title: `Senior Accountant`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 23, Name: `Thomas Burke`, Title: `Senior Accountant` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 22, Name: `Michael Anderson`, Title: `Junior Accountant` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 21, Name: `Roland Reyes`, Title: `Accountant Team Lead` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 24, Name: `Monica Mendel`, Title: `Senior Software Developer` })] + }), + new EmployeesNestedDataItem({ ID: 10, Age: 61, Salary: 85000, Productivity: 890, City: `Lyon`, Country: `France`, Phone: `259-266-887`, HireDate: `2010-01-01`, Name: `Yang Wang`, Title: `Localization Developer`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] + }), + new EmployeesNestedDataItem({ ID: 35, Age: 35, Salary: 75000, Productivity: 75, City: `Warasw`, Country: `Poland`, Phone: `688-244-844`, HireDate: `2014-01-22`, Name: `Janine Munoz`, Title: `HR`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` })] + }), + new EmployeesNestedDataItem({ ID: 10, Age: 49, Salary: 95000, Productivity: 80, City: `Krakow`, Country: `Poland`, Phone: `677-266-555`, HireDate: `2010-01-01`, Name: `Yang Wang`, Title: `Sales Manager`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] + }), + ]; + super(...newItems.slice(0)); + } + } +} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/NwindData.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/NwindData.json new file mode 100644 index 0000000000..c00b03ec8d --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/NwindData.json @@ -0,0 +1,458 @@ +[ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2012-02-12", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": true, + "OrderDate": "2003-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2006-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 4, + "ProductName": "Chef Antons Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2016-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 5, + "ProductName": "Chef Antons Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2011-11-11", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 6, + "ProductName": "Grandmas Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2017-12-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 7, + "ProductName": "Uncle Bobs Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 150, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2016-07-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2018-01-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2010-02-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2008-05-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": false, + "OrderDate": "2009-01-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2015-11-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2015-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2017-06-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2014-03-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + }, + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2018-03-28", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2015-08-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-09-27", + "Rating": 2, + "Locations": [ + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + }, + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2001-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + } + ] + }, + { + "ProductID": 20, + "ProductName": "Sir Rodneys Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "4 - 100 ml jars", + "UnitPrice": 4.5, + "UnitsInStock": 40, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + } +] \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css new file mode 100644 index 0000000000..3b330b391d --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css @@ -0,0 +1,7 @@ +/* shared styles are loaded from: */ +/* https://static.infragistics.com/xplatform/css/samples */ + + #grid { + --ig-size: var(--ig-size-medium); + } + diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx new file mode 100644 index 0000000000..a6d8f339e4 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx @@ -0,0 +1,379 @@ + +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import { + IgrTreeGrid, + IgrColumn, + IgrPaginator +} from 'igniteui-react-grids'; +import { IgrCheckbox } from 'igniteui-react'; +import { IgrList, IgrListItem, IgrListHeader } from 'igniteui-react'; +import { EmployeesNestedDataItem, EmployeesNestedData } from './EmployeesNestedData'; + +import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; + +type Employee = EmployeesNestedDataItem; +enum GridSection { + THEAD = 'thead', + TBODY = 'tbody', + FOOTER = 'tfoot', +} + +enum ItemAction { + Filterable, + Sortable, + Selectable, + Collapsible, + Editable, + Always, +} + +type Item = { + title: string; + subTitle: string; + action: ItemAction; + active: boolean; + completed: boolean; +}; + +const makeItem = ( + title: string, + subTitle: string, + completed: boolean, + action: ItemAction +): Item => ({ title, subTitle, completed, action, active: action === ItemAction.Always }); + +const theadKeyCombinations: Item[] = [ + makeItem('Space', 'select column', false, ItemAction.Selectable), + makeItem('Ctrl + ↑/↓', 'sort column asc/desc', false, ItemAction.Sortable), + makeItem('Ctrl + Shift + L', 'open Excel-style filter', false, ItemAction.Filterable), + makeItem('Alt + L', 'open Advanced filtering', false, ItemAction.Filterable), +]; + +const tbodyKeyCombinations: Item[] = [ + makeItem('Enter', 'enter edit mode', false, ItemAction.Editable), + makeItem('Alt + ←/↑', 'collapse row detail', false, ItemAction.Collapsible), + makeItem('Alt + →/↓', 'expand row detail', false, ItemAction.Collapsible), + makeItem('Ctrl + Home/End', 'jump to first/last cell', false, ItemAction.Always), +]; + +const summaryCombinations: Item[] = [ + makeItem('←', 'move left', false, ItemAction.Always), + makeItem('→', 'move right', false, ItemAction.Always), + makeItem('Home', 'first summary cell', false, ItemAction.Always), + makeItem('End', 'last summary cell', false, ItemAction.Always), +]; + +const cloneItems = (items: Item[]) => items.map(i => ({ ...i })); + +function enableActions(list: Item[], actions: ItemAction[]) { + return list.map(i => + i.action === ItemAction.Always + ? { ...i, active: true } + : { ...i, active: actions.includes(i.action) } + ); +} + +function markCompleted(list: Item[], idx: number, value: boolean) { + const next = list.slice(); + next[idx] = { ...next[idx], completed: value }; + return next; +} + +export default function TreeGridKeyboardNavGuide() { + const gridRef = useRef(null); + const lastKeyRef = useRef(''); + const isInEditModeRef = useRef(false); + + const [data] = useState(() => new EmployeesNestedData()); + const [gridSection, setGridSection] = useState(GridSection.THEAD); + const [items, setItems] = useState(cloneItems(theadKeyCombinations)); + + const checkEditMode = () => { + const editingCell = document.querySelector('.igx-grid__td--editing, .igc-grid__td--editing'); + const gridElement = (gridRef.current as any)?.nativeElement; + const inputInGrid = gridElement?.querySelector('input, textarea, select'); + return !!(editingCell || inputInGrid); + }; + + useEffect(() => { + const handleGlobalKeyDown = (evt: KeyboardEvent) => { + const key = evt.key?.toLowerCase(); + + const gridElement = (gridRef.current as any)?.nativeElement; + const focusedElement = document.activeElement; + + if (gridElement && (gridElement.contains(focusedElement) || gridElement === focusedElement)) { + if (evt.altKey && (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright')) { + lastKeyRef.current = key; + } + + if (evt.ctrlKey && (key === 'home' || key === 'end')) { + lastKeyRef.current = key; + + if (gridSection === GridSection.TBODY) { + setTimeout(() => { + setItems((prev: Item[]) => markCompleted(prev, 3, true)); + }, 0); + } + } + + if (key === 'enter') { + const focusedElement = document.activeElement; + + const focusedIsInput = focusedElement && ( + focusedElement.tagName === 'INPUT' || + focusedElement.tagName === 'TEXTAREA' || + focusedElement.tagName === 'SELECT' + ); + + const focusedCellInEditMode = focusedElement && focusedElement.closest('.igx-grid__td--editing, .igc-grid__td--editing'); + + const currentlyInEditMode = !!(focusedIsInput || focusedCellInEditMode); + + if (!currentlyInEditMode && gridSection === GridSection.TBODY) { + setItems((prev: Item[]) => markCompleted(prev, 0, true)); + } + + isInEditModeRef.current = currentlyInEditMode; + } + + if (key === 'escape') { + setTimeout(() => { + isInEditModeRef.current = checkEditMode(); + }, 10); + } + } + }; + + document.addEventListener('keydown', handleGlobalKeyDown, true); + + return () => { + document.removeEventListener('keydown', handleGlobalKeyDown, true); + }; + }, [gridSection]); + + const headerText = useMemo(() => { + if (gridSection === GridSection.THEAD) return 'HEADER COMBINATIONS'; + if (gridSection === GridSection.TBODY) return 'BODY COMBINATIONS'; + if (gridSection === GridSection.FOOTER) return 'SUMMARY COMBINATIONS'; + return ''; + }, [gridSection]); + + const updateSectionFromActiveNode = () => { + const grid = gridRef.current; + const active = (grid as any)?.navigation?.activeNode; + const rows = grid?.data?.length ?? data.length; + if (active && typeof active.row === 'number') { + if (active.row < 0) { + setGridSection(GridSection.THEAD); + if (gridSection !== GridSection.THEAD) { + setItems(cloneItems(theadKeyCombinations)); + } + } else if (active.row >= rows) { + setGridSection(GridSection.FOOTER); + if (gridSection !== GridSection.FOOTER) { + setItems(cloneItems(summaryCombinations)); + } + } else { + setGridSection(GridSection.TBODY); + if (gridSection !== GridSection.TBODY) { + setItems(cloneItems(tbodyKeyCombinations)); + } + } + } + }; + + const onGridKeyDown = (evt: any) => { + const key = evt.key?.toLowerCase(); + if (!key) return; + if (key === 'tab') return; + + if ((key === 'l' && evt.altKey) || + (key === 'l' && evt.ctrlKey && evt.shiftKey) || + ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) || + ((key === 'end' || key === 'home') && evt.ctrlKey)) { + evt.preventDefault(); + } + + updateSectionFromActiveNode(); + + setItems((prev: Item[]) => { + let next = prev.slice(); + + if (gridSection === GridSection.FOOTER) { + switch (key) { + case 'end': + next = markCompleted(next, 3, true); + break; + case 'home': + next = markCompleted(next, 2, true); + break; + case 'arrowleft': + next = markCompleted(next, 0, true); + break; + case 'arrowright': + next = markCompleted(next, 1, true); + break; + } + return next; + } + + if (gridSection === GridSection.THEAD) { + if (key === 'l' && evt.altKey) { + next = markCompleted(next, 3, true); + return next; + } + if (key === 'l' && evt.ctrlKey && evt.shiftKey) { + next = markCompleted(next, 2, true); + } + if ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) { + next = markCompleted(next, 1, true); + } + if (key === ' ' || key === 'spacebar') { + next = markCompleted(next, 0, true); + } + } + + return next; + }); + }; + + const refreshHeaderActions = () => { + const grid: any = gridRef.current; + if (!grid) return; + + if (gridSection !== GridSection.THEAD) return; + + const active = grid?.navigation?.activeNode; + const col = grid.visibleColumns?.find( + (c: any) => c.visibleIndex === active?.column && c.level === active?.level + ); + + const actions: ItemAction[] = []; + if (col?.sortable) actions.push(ItemAction.Sortable); + if (col?.filterable && !col?.columnGroup) actions.push(ItemAction.Filterable); + if (col?.selectable) actions.push(ItemAction.Selectable); + + setItems((prev: Item[]) => enableActions(prev, actions)); + }; + + const onActiveNodeChange = () => { + updateSectionFromActiveNode(); + refreshHeaderActions(); + + setTimeout(() => { + isInEditModeRef.current = checkEditMode(); + }, 10); + }; + + useEffect(() => { + const handleGlobalClick = () => { + setTimeout(() => { + isInEditModeRef.current = checkEditMode(); + }, 10); + }; + + document.addEventListener('click', handleGlobalClick, true); + + return () => { + document.removeEventListener('click', handleGlobalClick, true); + }; + }, []); + + const onRowToggle = () => { + if (gridSection === GridSection.TBODY || gridSection === GridSection.FOOTER) { + const lastKey = lastKeyRef.current; + setItems((prev: Item[]) => { + if (lastKey === 'arrowup' || lastKey === 'arrowleft') { + return markCompleted(prev, 1, true); + } else if (lastKey === 'arrowdown' || lastKey === 'arrowright') { + return markCompleted(prev, 2, true); + } + return prev; + }); + setTimeout(() => { + lastKeyRef.current = ''; + }, 100); + } + }; + + return ( +
+
+ + + + {/* Tree grid columns for Employees */} + + + + + + + + + + +
+ + {/* Right-side list showing active/available shortcuts */} +
+ + {items.length > 0 && ( + + {headerText} + + )} + {items.map((c: Item, idx: number) => ( + +
+
+

{c.title}

+

{c.subTitle}

+
+ { + const checked = !!e?.detail; + setItems((prev: Item[]) => markCompleted(prev, idx, checked)); + }} + /> +
+
+ ))} + + {items.length === 0 && ( +
+
Use the browser navigation until you reach one of the following grid sections:
+
    +
  • Header
  • +
  • Body
  • +
  • Summary
  • +
+
When reached, an action list will be shown.
+
+ )} +
+
+
+ ); +} + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render(); diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json new file mode 100644 index 0000000000..8c0d146f95 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true, + "baseUrl": ".", + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "noUnusedLocals": false, + "importHelpers": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": false, + "isolatedModules": true, + "noEmit": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts", + "**/odatajs-4.0.0.js", + "config-overrides.js" + ], + "include": [ + "src" + ] +} From 50c41eb84a0d4c4a3060ae539952abf1a991a394 Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 12:41:36 +0300 Subject: [PATCH 2/6] fix(samples): updated browser rendering --- samples/grids/grid/keyboard-custom-navigation/src/index.tsx | 4 ++-- .../keyboard-custom-navigation-hgrid/src/index.tsx | 3 ++- .../tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/samples/grids/grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/grid/keyboard-custom-navigation/src/index.tsx index 92bccca83b..0bad0f60c1 100644 --- a/samples/grids/grid/keyboard-custom-navigation/src/index.tsx +++ b/samples/grids/grid/keyboard-custom-navigation/src/index.tsx @@ -494,6 +494,6 @@ export default function GridKeyboardNavGuide() { ); } -// Render to DOM -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +// rendering above component in the React DOM +const root = ReactDOM.createRoot(document.getElementById('root')); root.render(); diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx index 943fa23c23..dd3a70277b 100644 --- a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx @@ -300,5 +300,6 @@ export default function HierarchicalGridKeyboardNavGuide() { ); } -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +// rendering above component in the React DOM +const root = ReactDOM.createRoot(document.getElementById('root')); root.render(); diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx index a6d8f339e4..dfe7ec8ea9 100644 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx +++ b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx @@ -375,5 +375,6 @@ export default function TreeGridKeyboardNavGuide() { ); } -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +// rendering above component in the React DOM +const root = ReactDOM.createRoot(document.getElementById('root')); root.render(); From 042b7fcdc516c49e2862a299192dfa31e7f8213e Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 13:00:02 +0300 Subject: [PATCH 3/6] chore(samples): renamed samples to be used as shared files in docs --- .../.eslintrc.js | 0 .../ReadMe.md | 8 +- .../package.json | 0 .../public/index.html | 0 .../sandbox.config.json | 0 .../src/NwindData.json | 0 .../src/SingersData.json | 0 .../src/index.css | 0 .../src/index.tsx | 0 .../src/react-app-env.d.ts | 0 .../tsconfig.json | 0 .../.eslintrc.js | 78 --- .../ReadMe.md | 56 --- .../package.json | 48 -- .../public/index.html | 11 - .../sandbox.config.json | 5 - .../src/EmployeesNestedData.ts | 75 --- .../src/index.css | 7 - .../src/index.tsx | 380 -------------- .../src/react-app-env.d.ts | 1 - .../tsconfig.json | 44 -- .../src/NwindData.json | 0 .../keyboard-custom-navigation/src/index.css | 5 + .../keyboard-custom-navigation/src/index.tsx | 469 +++++++++++++----- 24 files changed, 365 insertions(+), 822 deletions(-) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/.eslintrc.js (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/ReadMe.md (81%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/package.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/public/index.html (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/sandbox.config.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/src/NwindData.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/src/SingersData.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/src/index.css (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/src/index.tsx (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/src/react-app-env.d.ts (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation-hgrid => keyboard-custom-navigation}/tsconfig.json (100%) delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts delete mode 100644 samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json rename samples/grids/tree-grid/{keyboard-custom-navigation-tgrid => keyboard-custom-navigation}/src/NwindData.json (100%) diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/.eslintrc.js b/samples/grids/hierarchical-grid/keyboard-custom-navigation/.eslintrc.js similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/.eslintrc.js rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/.eslintrc.js diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md b/samples/grids/hierarchical-grid/keyboard-custom-navigation/ReadMe.md similarity index 81% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/ReadMe.md index ed2f5e5fc6..661e102428 100644 --- a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/ReadMe.md +++ b/samples/grids/hierarchical-grid/keyboard-custom-navigation/ReadMe.md @@ -1,7 +1,7 @@ -This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. +This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Hierarchical Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. @@ -12,10 +12,10 @@ This folder contains implementation of React application with example of Keyboar View Code - + Run Sample - + Run Sample @@ -34,7 +34,7 @@ Follow these instructions to run this example: git clone https://github.com/IgniteUI/igniteui-react-examples.git git checkout master cd ./igniteui-react-examples -cd ./samples/grids/grid/keyboard-custom-navigation +cd ./samples/grids/hierarchical-grid/keyboard-custom-navigation ``` open above folder in VS Code or type: diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/package.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation/package.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/package.json rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/package.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/public/index.html b/samples/grids/hierarchical-grid/keyboard-custom-navigation/public/index.html similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/public/index.html rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/public/index.html diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/sandbox.config.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation/sandbox.config.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/sandbox.config.json rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/sandbox.config.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/NwindData.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/NwindData.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/NwindData.json rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/src/NwindData.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/SingersData.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/SingersData.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/SingersData.json rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/src/SingersData.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.css b/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.css similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.css rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.css diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx b/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.tsx similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/index.tsx rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.tsx diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/react-app-env.d.ts b/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/react-app-env.d.ts similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/src/react-app-env.d.ts rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/src/react-app-env.d.ts diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/tsconfig.json b/samples/grids/hierarchical-grid/keyboard-custom-navigation/tsconfig.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation-hgrid/tsconfig.json rename to samples/grids/hierarchical-grid/keyboard-custom-navigation/tsconfig.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js deleted file mode 100644 index 7168b71441..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/.eslintrc.js +++ /dev/null @@ -1,78 +0,0 @@ -// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project -module.exports = { - parser: "@typescript-eslint/parser", // Specifies the ESLint parser - parserOptions: { - ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features - sourceType: "module", // Allows for the use of imports - ecmaFeatures: { - jsx: true // Allows for the parsing of JSX - } - }, - settings: { - react: { - version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use - } - }, - extends: [ - "eslint:recommended", - "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react - "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin - ], - rules: { - // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs - "default-case": "off", - "jsx-a11y/alt-text": "off", - "jsx-a11y/iframe-has-title": "off", - "no-undef": "off", - "no-unused-vars": "off", - "no-extend-native": "off", - "no-throw-literal": "off", - "no-useless-concat": "off", - "no-mixed-operators": "off", - "no-prototype-builtins": "off", - "no-mixed-spaces-and-tabs": 0, - "prefer-const": "off", - "prefer-rest-params": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-useless-constructor": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/prefer-namespace-keyword": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off" - }, - "overrides": [ - { - "files": ["*.ts", "*.tsx"], - "rules": { - "default-case": "off", - "jsx-a11y/alt-text": "off", - "jsx-a11y/iframe-has-title": "off", - "no-var": "off", - "no-undef": "off", - "no-unused-vars": "off", - "no-extend-native": "off", - "no-throw-literal": "off", - "no-useless-concat": "off", - "no-mixed-operators": "off", - "no-mixed-spaces-and-tabs": 0, - "no-prototype-builtins": "off", - "prefer-const": "off", - "prefer-rest-params": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-useless-constructor": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/prefer-namespace-keyword": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off" - } - } - ] - }; \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md deleted file mode 100644 index ed2f5e5fc6..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/ReadMe.md +++ /dev/null @@ -1,56 +0,0 @@ - - - -This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. - - - - - - View Docs - - - View Code - - - Run Sample - - - Run Sample - - - - -## Branches - -> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository. - -## Instructions - -Follow these instructions to run this example: - - -``` -git clone https://github.com/IgniteUI/igniteui-react-examples.git -git checkout master -cd ./igniteui-react-examples -cd ./samples/grids/grid/keyboard-custom-navigation -``` - -open above folder in VS Code or type: -``` -code . -``` - -In terminal window, run: -``` -npm install --legacy-peer-deps -npm run-script start -``` - -Then open http://localhost:4200/ in your browser - - -## Learn More - -To learn more about **Ignite UI for React** components, check out the [React documentation](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html). diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json deleted file mode 100644 index 9246bcd77a..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "example-ignite-ui-react", - "description": "This project provides example of using Ignite UI for React components", - "author": "Infragistics", - "version": "1.4.0", - "license": "", - "homepage": ".", - "private": true, - "scripts": { - "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start", - "build": "react-scripts --max_old_space_size=10240 build ", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject", - "lint": "eslint ./src/**/*.{ts,tsx}" - }, - "dependencies": { - "igniteui-dockmanager": "^1.17.0", - "igniteui-react": "^19.2.0", - "igniteui-react-core": "^19.0.1", - "igniteui-react-grids": "^19.2.0", - "igniteui-react-inputs": "^19.0.1", - "igniteui-react-layouts": "^19.0.1", - "igniteui-webcomponents": "^6.2.0", - "lit-html": "^3.2.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-scripts": "^5.0.1", - "tslib": "^2.4.0" - }, - "devDependencies": { - "@types/jest": "^29.2.0", - "@types/node": "^18.11.7", - "@types/react": "^18.0.24", - "@types/react-dom": "^18.0.8", - "eslint": "^8.33.0", - "eslint-config-react": "^1.1.7", - "eslint-plugin-react": "^7.20.0", - "react-app-rewired": "^2.2.1", - "typescript": "^4.8.4", - "worker-loader": "^3.0.8" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" - ] -} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html deleted file mode 100644 index e2d3265576..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/public/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Sample | Ignite UI | React | infragistics - - - - -
- - \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json deleted file mode 100644 index 07f53508eb..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/sandbox.config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "infiniteLoopProtection": false, - "hardReloadOnChange": false, - "view": "browser" -} \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts deleted file mode 100644 index 0728ee874d..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/EmployeesNestedData.ts +++ /dev/null @@ -1,75 +0,0 @@ -export class EmployeesNestedDataItem { - public constructor(init: Partial) { - Object.assign(this, init); - } - - public ID: number; - public Age: number; - public Salary: number; - public Productivity: number; - public City: string; - public Country: string; - public Phone: string; - public HireDate: string; - public Name: string; - public Title: string; - public Employees: EmployeesNestedDataItem_EmployeesItem[]; - -} -export class EmployeesNestedDataItem_EmployeesItem { - public constructor(init: Partial) { - Object.assign(this, init); - } - - public Age: number; - public Salary: number; - public Productivity: number; - public City: string; - public Country: string; - public Phone: string; - public HireDate: string; - public ID: number; - public Name: string; - public Title: string; - -} -export class EmployeesNestedData extends Array { - public constructor(items: Array | number = -1) { - if (Array.isArray(items)) { - super(...items); - } else { - const newItems = [ - new EmployeesNestedDataItem({ ID: 1, Age: 55, Salary: 80000, Productivity: 90, City: `Berlin`, Country: `Germany`, Phone: `609-202-505`, HireDate: `2008-03-20`, Name: `John Winchester`, Title: `Development Manager`, Employees: [ - new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Title: `Senior Software Developer` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Title: `Senior Software Developer` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] - }), - new EmployeesNestedDataItem({ ID: 4, Age: 42, Salary: 90000, Productivity: 80, City: `Kielce`, Country: `Poland`, Phone: `609-202-505`, HireDate: `2014-01-22`, Name: `Ana Sanders`, Title: `CEO`, Employees: [ - new EmployeesNestedDataItem_EmployeesItem({ Age: 44, Salary: 80000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-202-505`, HireDate: `2014-04-04`, ID: 14, Name: `Laurence Johnson`, Title: `Director` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 25, Salary: 85000, Productivity: 55, City: `Paris`, Country: `France`, Phone: `609-202-505`, HireDate: `2017-11-09`, ID: 5, Name: `Elizabeth Richards`, Title: `Vice President` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 39, Salary: 88000, Productivity: 88, City: `London`, Country: `UK`, Phone: `609-202-505`, HireDate: `2010-03-22`, ID: 13, Name: `Trevor Ashworth`, Title: `Director` })] - }), - new EmployeesNestedDataItem({ ID: 18, Age: 49, Salary: 77000, Productivity: 70, City: `Manchester`, Country: `UK`, Phone: `222-555-577`, HireDate: `2014-01-22`, Name: `Victoria Lincoln`, Title: `Senior Accountant`, Employees: [ - new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 23, Name: `Thomas Burke`, Title: `Senior Accountant` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 22, Name: `Michael Anderson`, Title: `Junior Accountant` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 21, Name: `Roland Reyes`, Title: `Accountant Team Lead` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 24, Name: `Monica Mendel`, Title: `Senior Software Developer` })] - }), - new EmployeesNestedDataItem({ ID: 10, Age: 61, Salary: 85000, Productivity: 890, City: `Lyon`, Country: `France`, Phone: `259-266-887`, HireDate: `2010-01-01`, Name: `Yang Wang`, Title: `Localization Developer`, Employees: [ - new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] - }), - new EmployeesNestedDataItem({ ID: 35, Age: 35, Salary: 75000, Productivity: 75, City: `Warasw`, Country: `Poland`, Phone: `688-244-844`, HireDate: `2014-01-22`, Name: `Janine Munoz`, Title: `HR`, Employees: [ - new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Title: `Senior Software Developer` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` })] - }), - new EmployeesNestedDataItem({ ID: 10, Age: 49, Salary: 95000, Productivity: 80, City: `Krakow`, Country: `Poland`, Phone: `677-266-555`, HireDate: `2010-01-01`, Name: `Yang Wang`, Title: `Sales Manager`, Employees: [ - new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Title: `Senior Software Developer` }), - new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] - }), - ]; - super(...newItems.slice(0)); - } - } -} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css deleted file mode 100644 index 3b330b391d..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.css +++ /dev/null @@ -1,7 +0,0 @@ -/* shared styles are loaded from: */ -/* https://static.infragistics.com/xplatform/css/samples */ - - #grid { - --ig-size: var(--ig-size-medium); - } - diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx deleted file mode 100644 index dfe7ec8ea9..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/index.tsx +++ /dev/null @@ -1,380 +0,0 @@ - -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import { - IgrTreeGrid, - IgrColumn, - IgrPaginator -} from 'igniteui-react-grids'; -import { IgrCheckbox } from 'igniteui-react'; -import { IgrList, IgrListItem, IgrListHeader } from 'igniteui-react'; -import { EmployeesNestedDataItem, EmployeesNestedData } from './EmployeesNestedData'; - -import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; - -type Employee = EmployeesNestedDataItem; -enum GridSection { - THEAD = 'thead', - TBODY = 'tbody', - FOOTER = 'tfoot', -} - -enum ItemAction { - Filterable, - Sortable, - Selectable, - Collapsible, - Editable, - Always, -} - -type Item = { - title: string; - subTitle: string; - action: ItemAction; - active: boolean; - completed: boolean; -}; - -const makeItem = ( - title: string, - subTitle: string, - completed: boolean, - action: ItemAction -): Item => ({ title, subTitle, completed, action, active: action === ItemAction.Always }); - -const theadKeyCombinations: Item[] = [ - makeItem('Space', 'select column', false, ItemAction.Selectable), - makeItem('Ctrl + ↑/↓', 'sort column asc/desc', false, ItemAction.Sortable), - makeItem('Ctrl + Shift + L', 'open Excel-style filter', false, ItemAction.Filterable), - makeItem('Alt + L', 'open Advanced filtering', false, ItemAction.Filterable), -]; - -const tbodyKeyCombinations: Item[] = [ - makeItem('Enter', 'enter edit mode', false, ItemAction.Editable), - makeItem('Alt + ←/↑', 'collapse row detail', false, ItemAction.Collapsible), - makeItem('Alt + →/↓', 'expand row detail', false, ItemAction.Collapsible), - makeItem('Ctrl + Home/End', 'jump to first/last cell', false, ItemAction.Always), -]; - -const summaryCombinations: Item[] = [ - makeItem('←', 'move left', false, ItemAction.Always), - makeItem('→', 'move right', false, ItemAction.Always), - makeItem('Home', 'first summary cell', false, ItemAction.Always), - makeItem('End', 'last summary cell', false, ItemAction.Always), -]; - -const cloneItems = (items: Item[]) => items.map(i => ({ ...i })); - -function enableActions(list: Item[], actions: ItemAction[]) { - return list.map(i => - i.action === ItemAction.Always - ? { ...i, active: true } - : { ...i, active: actions.includes(i.action) } - ); -} - -function markCompleted(list: Item[], idx: number, value: boolean) { - const next = list.slice(); - next[idx] = { ...next[idx], completed: value }; - return next; -} - -export default function TreeGridKeyboardNavGuide() { - const gridRef = useRef(null); - const lastKeyRef = useRef(''); - const isInEditModeRef = useRef(false); - - const [data] = useState(() => new EmployeesNestedData()); - const [gridSection, setGridSection] = useState(GridSection.THEAD); - const [items, setItems] = useState(cloneItems(theadKeyCombinations)); - - const checkEditMode = () => { - const editingCell = document.querySelector('.igx-grid__td--editing, .igc-grid__td--editing'); - const gridElement = (gridRef.current as any)?.nativeElement; - const inputInGrid = gridElement?.querySelector('input, textarea, select'); - return !!(editingCell || inputInGrid); - }; - - useEffect(() => { - const handleGlobalKeyDown = (evt: KeyboardEvent) => { - const key = evt.key?.toLowerCase(); - - const gridElement = (gridRef.current as any)?.nativeElement; - const focusedElement = document.activeElement; - - if (gridElement && (gridElement.contains(focusedElement) || gridElement === focusedElement)) { - if (evt.altKey && (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright')) { - lastKeyRef.current = key; - } - - if (evt.ctrlKey && (key === 'home' || key === 'end')) { - lastKeyRef.current = key; - - if (gridSection === GridSection.TBODY) { - setTimeout(() => { - setItems((prev: Item[]) => markCompleted(prev, 3, true)); - }, 0); - } - } - - if (key === 'enter') { - const focusedElement = document.activeElement; - - const focusedIsInput = focusedElement && ( - focusedElement.tagName === 'INPUT' || - focusedElement.tagName === 'TEXTAREA' || - focusedElement.tagName === 'SELECT' - ); - - const focusedCellInEditMode = focusedElement && focusedElement.closest('.igx-grid__td--editing, .igc-grid__td--editing'); - - const currentlyInEditMode = !!(focusedIsInput || focusedCellInEditMode); - - if (!currentlyInEditMode && gridSection === GridSection.TBODY) { - setItems((prev: Item[]) => markCompleted(prev, 0, true)); - } - - isInEditModeRef.current = currentlyInEditMode; - } - - if (key === 'escape') { - setTimeout(() => { - isInEditModeRef.current = checkEditMode(); - }, 10); - } - } - }; - - document.addEventListener('keydown', handleGlobalKeyDown, true); - - return () => { - document.removeEventListener('keydown', handleGlobalKeyDown, true); - }; - }, [gridSection]); - - const headerText = useMemo(() => { - if (gridSection === GridSection.THEAD) return 'HEADER COMBINATIONS'; - if (gridSection === GridSection.TBODY) return 'BODY COMBINATIONS'; - if (gridSection === GridSection.FOOTER) return 'SUMMARY COMBINATIONS'; - return ''; - }, [gridSection]); - - const updateSectionFromActiveNode = () => { - const grid = gridRef.current; - const active = (grid as any)?.navigation?.activeNode; - const rows = grid?.data?.length ?? data.length; - if (active && typeof active.row === 'number') { - if (active.row < 0) { - setGridSection(GridSection.THEAD); - if (gridSection !== GridSection.THEAD) { - setItems(cloneItems(theadKeyCombinations)); - } - } else if (active.row >= rows) { - setGridSection(GridSection.FOOTER); - if (gridSection !== GridSection.FOOTER) { - setItems(cloneItems(summaryCombinations)); - } - } else { - setGridSection(GridSection.TBODY); - if (gridSection !== GridSection.TBODY) { - setItems(cloneItems(tbodyKeyCombinations)); - } - } - } - }; - - const onGridKeyDown = (evt: any) => { - const key = evt.key?.toLowerCase(); - if (!key) return; - if (key === 'tab') return; - - if ((key === 'l' && evt.altKey) || - (key === 'l' && evt.ctrlKey && evt.shiftKey) || - ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) || - ((key === 'end' || key === 'home') && evt.ctrlKey)) { - evt.preventDefault(); - } - - updateSectionFromActiveNode(); - - setItems((prev: Item[]) => { - let next = prev.slice(); - - if (gridSection === GridSection.FOOTER) { - switch (key) { - case 'end': - next = markCompleted(next, 3, true); - break; - case 'home': - next = markCompleted(next, 2, true); - break; - case 'arrowleft': - next = markCompleted(next, 0, true); - break; - case 'arrowright': - next = markCompleted(next, 1, true); - break; - } - return next; - } - - if (gridSection === GridSection.THEAD) { - if (key === 'l' && evt.altKey) { - next = markCompleted(next, 3, true); - return next; - } - if (key === 'l' && evt.ctrlKey && evt.shiftKey) { - next = markCompleted(next, 2, true); - } - if ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) { - next = markCompleted(next, 1, true); - } - if (key === ' ' || key === 'spacebar') { - next = markCompleted(next, 0, true); - } - } - - return next; - }); - }; - - const refreshHeaderActions = () => { - const grid: any = gridRef.current; - if (!grid) return; - - if (gridSection !== GridSection.THEAD) return; - - const active = grid?.navigation?.activeNode; - const col = grid.visibleColumns?.find( - (c: any) => c.visibleIndex === active?.column && c.level === active?.level - ); - - const actions: ItemAction[] = []; - if (col?.sortable) actions.push(ItemAction.Sortable); - if (col?.filterable && !col?.columnGroup) actions.push(ItemAction.Filterable); - if (col?.selectable) actions.push(ItemAction.Selectable); - - setItems((prev: Item[]) => enableActions(prev, actions)); - }; - - const onActiveNodeChange = () => { - updateSectionFromActiveNode(); - refreshHeaderActions(); - - setTimeout(() => { - isInEditModeRef.current = checkEditMode(); - }, 10); - }; - - useEffect(() => { - const handleGlobalClick = () => { - setTimeout(() => { - isInEditModeRef.current = checkEditMode(); - }, 10); - }; - - document.addEventListener('click', handleGlobalClick, true); - - return () => { - document.removeEventListener('click', handleGlobalClick, true); - }; - }, []); - - const onRowToggle = () => { - if (gridSection === GridSection.TBODY || gridSection === GridSection.FOOTER) { - const lastKey = lastKeyRef.current; - setItems((prev: Item[]) => { - if (lastKey === 'arrowup' || lastKey === 'arrowleft') { - return markCompleted(prev, 1, true); - } else if (lastKey === 'arrowdown' || lastKey === 'arrowright') { - return markCompleted(prev, 2, true); - } - return prev; - }); - setTimeout(() => { - lastKeyRef.current = ''; - }, 100); - } - }; - - return ( -
-
- - - - {/* Tree grid columns for Employees */} - - - - - - - - - - -
- - {/* Right-side list showing active/available shortcuts */} -
- - {items.length > 0 && ( - - {headerText} - - )} - {items.map((c: Item, idx: number) => ( - -
-
-

{c.title}

-

{c.subTitle}

-
- { - const checked = !!e?.detail; - setItems((prev: Item[]) => markCompleted(prev, idx, checked)); - }} - /> -
-
- ))} - - {items.length === 0 && ( -
-
Use the browser navigation until you reach one of the following grid sections:
-
    -
  • Header
  • -
  • Body
  • -
  • Summary
  • -
-
When reached, an action list will be shown.
-
- )} -
-
-
- ); -} - -// rendering above component in the React DOM -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render(); diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc6..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json b/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json deleted file mode 100644 index 8c0d146f95..0000000000 --- a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/tsconfig.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "compilerOptions": { - "resolveJsonModule": true, - "esModuleInterop": true, - "baseUrl": ".", - "outDir": "build/dist", - "module": "esnext", - "target": "es5", - "lib": [ - "es6", - "dom" - ], - "sourceMap": true, - "allowJs": true, - "jsx": "react-jsx", - "moduleResolution": "node", - "rootDir": "src", - "forceConsistentCasingInFileNames": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noImplicitAny": true, - "noUnusedLocals": false, - "importHelpers": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true, - "strict": false, - "isolatedModules": true, - "noEmit": true - }, - "exclude": [ - "node_modules", - "build", - "scripts", - "acceptance-tests", - "webpack", - "jest", - "src/setupTests.ts", - "**/odatajs-4.0.0.js", - "config-overrides.js" - ], - "include": [ - "src" - ] -} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/NwindData.json b/samples/grids/tree-grid/keyboard-custom-navigation/src/NwindData.json similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation-tgrid/src/NwindData.json rename to samples/grids/tree-grid/keyboard-custom-navigation/src/NwindData.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css index 98682b8543..3b330b391d 100644 --- a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css +++ b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css @@ -1,2 +1,7 @@ /* shared styles are loaded from: */ /* https://static.infragistics.com/xplatform/css/samples */ + + #grid { + --ig-size: var(--ig-size-medium); + } + diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx index 9488b0587c..dfe7ec8ea9 100644 --- a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx +++ b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx @@ -1,137 +1,380 @@ -import React from 'react'; + +import React, { useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; - -import { IgrPropertyEditorPanelModule } from 'igniteui-react-layouts'; -import { IgrTreeGridModule } from 'igniteui-react-grids'; -import { IgrTreeGrid, IgrPaginator, IgrColumn } from 'igniteui-react-grids'; -import { ComponentRenderer, PropertyEditorPanelDescriptionModule, WebTreeGridDescriptionModule } from 'igniteui-react-core'; -import { EmployeesNestedDataItem, EmployeesNestedDataItem_EmployeesItem, EmployeesNestedData } from './EmployeesNestedData'; -import { IgrGrid, IgrGridKeydownEventArgs } from 'igniteui-react-grids'; +import { + IgrTreeGrid, + IgrColumn, + IgrPaginator +} from 'igniteui-react-grids'; +import { IgrCheckbox } from 'igniteui-react'; +import { IgrList, IgrListItem, IgrListHeader } from 'igniteui-react'; +import { EmployeesNestedDataItem, EmployeesNestedData } from './EmployeesNestedData'; import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; -const mods: any[] = [ - IgrPropertyEditorPanelModule, - IgrTreeGridModule +type Employee = EmployeesNestedDataItem; +enum GridSection { + THEAD = 'thead', + TBODY = 'tbody', + FOOTER = 'tfoot', +} + +enum ItemAction { + Filterable, + Sortable, + Selectable, + Collapsible, + Editable, + Always, +} + +type Item = { + title: string; + subTitle: string; + action: ItemAction; + active: boolean; + completed: boolean; +}; + +const makeItem = ( + title: string, + subTitle: string, + completed: boolean, + action: ItemAction +): Item => ({ title, subTitle, completed, action, active: action === ItemAction.Always }); + +const theadKeyCombinations: Item[] = [ + makeItem('Space', 'select column', false, ItemAction.Selectable), + makeItem('Ctrl + ↑/↓', 'sort column asc/desc', false, ItemAction.Sortable), + makeItem('Ctrl + Shift + L', 'open Excel-style filter', false, ItemAction.Filterable), + makeItem('Alt + L', 'open Advanced filtering', false, ItemAction.Filterable), ]; -mods.forEach((m) => m.register()); -export default class Sample extends React.Component { - private treeGrid: IgrTreeGrid - private treeGridRef(r: IgrTreeGrid) { - this.treeGrid = r; - this.setState({}); - } +const tbodyKeyCombinations: Item[] = [ + makeItem('Enter', 'enter edit mode', false, ItemAction.Editable), + makeItem('Alt + ←/↑', 'collapse row detail', false, ItemAction.Collapsible), + makeItem('Alt + →/↓', 'expand row detail', false, ItemAction.Collapsible), + makeItem('Ctrl + Home/End', 'jump to first/last cell', false, ItemAction.Always), +]; + +const summaryCombinations: Item[] = [ + makeItem('←', 'move left', false, ItemAction.Always), + makeItem('→', 'move right', false, ItemAction.Always), + makeItem('Home', 'first summary cell', false, ItemAction.Always), + makeItem('End', 'last summary cell', false, ItemAction.Always), +]; + +const cloneItems = (items: Item[]) => items.map(i => ({ ...i })); + +function enableActions(list: Item[], actions: ItemAction[]) { + return list.map(i => + i.action === ItemAction.Always + ? { ...i, active: true } + : { ...i, active: actions.includes(i.action) } + ); +} + +function markCompleted(list: Item[], idx: number, value: boolean) { + const next = list.slice(); + next[idx] = { ...next[idx], completed: value }; + return next; +} + +export default function TreeGridKeyboardNavGuide() { + const gridRef = useRef(null); + const lastKeyRef = useRef(''); + const isInEditModeRef = useRef(false); + + const [data] = useState(() => new EmployeesNestedData()); + const [gridSection, setGridSection] = useState(GridSection.THEAD); + const [items, setItems] = useState(cloneItems(theadKeyCombinations)); + + const checkEditMode = () => { + const editingCell = document.querySelector('.igx-grid__td--editing, .igc-grid__td--editing'); + const gridElement = (gridRef.current as any)?.nativeElement; + const inputInGrid = gridElement?.querySelector('input, textarea, select'); + return !!(editingCell || inputInGrid); + }; + + useEffect(() => { + const handleGlobalKeyDown = (evt: KeyboardEvent) => { + const key = evt.key?.toLowerCase(); + + const gridElement = (gridRef.current as any)?.nativeElement; + const focusedElement = document.activeElement; + + if (gridElement && (gridElement.contains(focusedElement) || gridElement === focusedElement)) { + if (evt.altKey && (key === 'arrowup' || key === 'arrowdown' || key === 'arrowleft' || key === 'arrowright')) { + lastKeyRef.current = key; + } + + if (evt.ctrlKey && (key === 'home' || key === 'end')) { + lastKeyRef.current = key; + + if (gridSection === GridSection.TBODY) { + setTimeout(() => { + setItems((prev: Item[]) => markCompleted(prev, 3, true)); + }, 0); + } + } + + if (key === 'enter') { + const focusedElement = document.activeElement; + + const focusedIsInput = focusedElement && ( + focusedElement.tagName === 'INPUT' || + focusedElement.tagName === 'TEXTAREA' || + focusedElement.tagName === 'SELECT' + ); + + const focusedCellInEditMode = focusedElement && focusedElement.closest('.igx-grid__td--editing, .igc-grid__td--editing'); + + const currentlyInEditMode = !!(focusedIsInput || focusedCellInEditMode); + + if (!currentlyInEditMode && gridSection === GridSection.TBODY) { + setItems((prev: Item[]) => markCompleted(prev, 0, true)); + } + + isInEditModeRef.current = currentlyInEditMode; + } + + if (key === 'escape') { + setTimeout(() => { + isInEditModeRef.current = checkEditMode(); + }, 10); + } + } + }; - constructor(props: any) { - super(props); + document.addEventListener('keydown', handleGlobalKeyDown, true); + + return () => { + document.removeEventListener('keydown', handleGlobalKeyDown, true); + }; + }, [gridSection]); - this.treeGridRef = this.treeGridRef.bind(this); - this.webGridCustomKBNav = this.webGridCustomKBNav.bind(this); + const headerText = useMemo(() => { + if (gridSection === GridSection.THEAD) return 'HEADER COMBINATIONS'; + if (gridSection === GridSection.TBODY) return 'BODY COMBINATIONS'; + if (gridSection === GridSection.FOOTER) return 'SUMMARY COMBINATIONS'; + return ''; + }, [gridSection]); + + const updateSectionFromActiveNode = () => { + const grid = gridRef.current; + const active = (grid as any)?.navigation?.activeNode; + const rows = grid?.data?.length ?? data.length; + if (active && typeof active.row === 'number') { + if (active.row < 0) { + setGridSection(GridSection.THEAD); + if (gridSection !== GridSection.THEAD) { + setItems(cloneItems(theadKeyCombinations)); + } + } else if (active.row >= rows) { + setGridSection(GridSection.FOOTER); + if (gridSection !== GridSection.FOOTER) { + setItems(cloneItems(summaryCombinations)); + } + } else { + setGridSection(GridSection.TBODY); + if (gridSection !== GridSection.TBODY) { + setItems(cloneItems(tbodyKeyCombinations)); + } + } } + }; - public render(): JSX.Element { - return ( -
- -
- - - - - - - - - - -
-
- ); + const onGridKeyDown = (evt: any) => { + const key = evt.key?.toLowerCase(); + if (!key) return; + if (key === 'tab') return; + + if ((key === 'l' && evt.altKey) || + (key === 'l' && evt.ctrlKey && evt.shiftKey) || + ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) || + ((key === 'end' || key === 'home') && evt.ctrlKey)) { + evt.preventDefault(); } - private _employeesNestedData: EmployeesNestedData = null; - public get employeesNestedData(): EmployeesNestedData { - if (this._employeesNestedData == null) - { - this._employeesNestedData = new EmployeesNestedData(); + updateSectionFromActiveNode(); + + setItems((prev: Item[]) => { + let next = prev.slice(); + + if (gridSection === GridSection.FOOTER) { + switch (key) { + case 'end': + next = markCompleted(next, 3, true); + break; + case 'home': + next = markCompleted(next, 2, true); + break; + case 'arrowleft': + next = markCompleted(next, 0, true); + break; + case 'arrowright': + next = markCompleted(next, 1, true); + break; } - return this._employeesNestedData; - } + return next; + } - private _componentRenderer: ComponentRenderer = null; - public get renderer(): ComponentRenderer { - if (this._componentRenderer == null) { - this._componentRenderer = new ComponentRenderer(); - var context = this._componentRenderer.context; - PropertyEditorPanelDescriptionModule.register(context); - WebTreeGridDescriptionModule.register(context); + if (gridSection === GridSection.THEAD) { + if (key === 'l' && evt.altKey) { + next = markCompleted(next, 3, true); + return next; } - return this._componentRenderer; - } + if (key === 'l' && evt.ctrlKey && evt.shiftKey) { + next = markCompleted(next, 2, true); + } + if ((key === 'arrowup' || key === 'arrowdown') && evt.ctrlKey) { + next = markCompleted(next, 1, true); + } + if (key === ' ' || key === 'spacebar') { + next = markCompleted(next, 0, true); + } + } - public webGridCustomKBNav(eventArgs: IgrGridKeydownEventArgs): void { - const args = eventArgs.detail; - const target = args.target; - const evt = args.event; - const type = args.targetType; - const grid = eventArgs.target as IgrGrid; - - if (type === 'dataCell' && target.editMode && evt.key.toLowerCase() === 'tab') { - // Value validation for number column. - // This covers both 'tab' and 'shift+tab' key interactions. - args.event.preventDefault(); - args.cancel = true; - if (target.column.dataType === 'number' && target.editValue < 10) { - alert('The value should be bigger than 10'); - return; - } - const cell = evt.shiftKey ? - grid.getPreviousCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable) : - grid.getNextCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable); - - grid.navigateTo(cell.rowIndex, cell.visibleColumnIndex, - (obj: any) => { obj.target.activate(); }); - } else if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') { - // Perform column based kb navigation with 'enter' key press - args.cancel = true; - grid.navigateTo(target.row.index + 1, target.column.visibleIndex, (obj: any) => { - obj.target.activate(); - }); + return next; + }); + }; + + const refreshHeaderActions = () => { + const grid: any = gridRef.current; + if (!grid) return; + + if (gridSection !== GridSection.THEAD) return; + + const active = grid?.navigation?.activeNode; + const col = grid.visibleColumns?.find( + (c: any) => c.visibleIndex === active?.column && c.level === active?.level + ); + + const actions: ItemAction[] = []; + if (col?.sortable) actions.push(ItemAction.Sortable); + if (col?.filterable && !col?.columnGroup) actions.push(ItemAction.Filterable); + if (col?.selectable) actions.push(ItemAction.Selectable); + + setItems((prev: Item[]) => enableActions(prev, actions)); + }; + + const onActiveNodeChange = () => { + updateSectionFromActiveNode(); + refreshHeaderActions(); + + setTimeout(() => { + isInEditModeRef.current = checkEditMode(); + }, 10); + }; + + useEffect(() => { + const handleGlobalClick = () => { + setTimeout(() => { + isInEditModeRef.current = checkEditMode(); + }, 10); + }; + + document.addEventListener('click', handleGlobalClick, true); + + return () => { + document.removeEventListener('click', handleGlobalClick, true); + }; + }, []); + + const onRowToggle = () => { + if (gridSection === GridSection.TBODY || gridSection === GridSection.FOOTER) { + const lastKey = lastKeyRef.current; + setItems((prev: Item[]) => { + if (lastKey === 'arrowup' || lastKey === 'arrowleft') { + return markCompleted(prev, 1, true); + } else if (lastKey === 'arrowdown' || lastKey === 'arrowright') { + return markCompleted(prev, 2, true); } + return prev; + }); + setTimeout(() => { + lastKeyRef.current = ''; + }, 100); } + }; + + return ( +
+
+ + + {/* Tree grid columns for Employees */} + + + + + + + + + + +
+ + {/* Right-side list showing active/available shortcuts */} +
+ + {items.length > 0 && ( + + {headerText} + + )} + {items.map((c: Item, idx: number) => ( + +
+
+

{c.title}

+

{c.subTitle}

+
+ { + const checked = !!e?.detail; + setItems((prev: Item[]) => markCompleted(prev, idx, checked)); + }} + /> +
+
+ ))} + + {items.length === 0 && ( +
+
Use the browser navigation until you reach one of the following grid sections:
+
    +
  • Header
  • +
  • Body
  • +
  • Summary
  • +
+
When reached, an action list will be shown.
+
+ )} +
+
+
+ ); } // rendering above component in the React DOM const root = ReactDOM.createRoot(document.getElementById('root')); -root.render(); \ No newline at end of file +root.render(); From 9052ec9fd5c32974c3e5dcf0a1e5124b96d92a22 Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 13:14:12 +0300 Subject: [PATCH 4/6] chore(samples): updated samples with correct names --- .../.eslintrc.js | 0 .../ReadMe.md | 0 .../package.json | 0 .../public/index.html | 0 .../sandbox.config.json | 0 .../src/NwindData.json | 0 .../src/index.css | 0 .../src/index.tsx | 0 .../src/react-app-env.d.ts | 0 .../tsconfig.json | 0 .../.eslintrc.js | 0 .../ReadMe.md | 0 .../package.json | 0 .../public/index.html | 0 .../sandbox.config.json | 0 .../src/NwindData.json | 0 .../src/SingersData.json | 0 .../src/index.css | 0 .../src/index.tsx | 0 .../src/react-app-env.d.ts | 0 .../tsconfig.json | 0 .../.eslintrc.js | 0 .../ReadMe.md | 0 .../package.json | 0 .../public/index.html | 0 .../sandbox.config.json | 0 .../src/EmployeesNestedData.ts | 0 .../src/NwindData.json | 458 ++++++++++++++++++ .../src/index.css | 0 .../src/index.tsx | 0 .../src/react-app-env.d.ts | 0 .../tsconfig.json | 0 32 files changed, 458 insertions(+) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/.eslintrc.js (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/ReadMe.md (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/package.json (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/public/index.html (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/sandbox.config.json (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/NwindData.json (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/index.css (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/index.tsx (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/react-app-env.d.ts (100%) rename samples/grids/grid/{keyboard-custom-navigation => keyboard-navigation-guide}/tsconfig.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/.eslintrc.js (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/ReadMe.md (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/package.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/public/index.html (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/sandbox.config.json (100%) rename samples/grids/{tree-grid/keyboard-custom-navigation => hierarchical-grid/keyboard-navigation-guide}/src/NwindData.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/SingersData.json (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/index.css (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/index.tsx (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/react-app-env.d.ts (100%) rename samples/grids/hierarchical-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/tsconfig.json (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/.eslintrc.js (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/ReadMe.md (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/package.json (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/public/index.html (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/sandbox.config.json (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/EmployeesNestedData.ts (100%) create mode 100644 samples/grids/tree-grid/keyboard-navigation-guide/src/NwindData.json rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/index.css (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/index.tsx (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/src/react-app-env.d.ts (100%) rename samples/grids/tree-grid/{keyboard-custom-navigation => keyboard-navigation-guide}/tsconfig.json (100%) diff --git a/samples/grids/grid/keyboard-custom-navigation/.eslintrc.js b/samples/grids/grid/keyboard-navigation-guide/.eslintrc.js similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/.eslintrc.js rename to samples/grids/grid/keyboard-navigation-guide/.eslintrc.js diff --git a/samples/grids/grid/keyboard-custom-navigation/ReadMe.md b/samples/grids/grid/keyboard-navigation-guide/ReadMe.md similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/ReadMe.md rename to samples/grids/grid/keyboard-navigation-guide/ReadMe.md diff --git a/samples/grids/grid/keyboard-custom-navigation/package.json b/samples/grids/grid/keyboard-navigation-guide/package.json similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/package.json rename to samples/grids/grid/keyboard-navigation-guide/package.json diff --git a/samples/grids/grid/keyboard-custom-navigation/public/index.html b/samples/grids/grid/keyboard-navigation-guide/public/index.html similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/public/index.html rename to samples/grids/grid/keyboard-navigation-guide/public/index.html diff --git a/samples/grids/grid/keyboard-custom-navigation/sandbox.config.json b/samples/grids/grid/keyboard-navigation-guide/sandbox.config.json similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/sandbox.config.json rename to samples/grids/grid/keyboard-navigation-guide/sandbox.config.json diff --git a/samples/grids/grid/keyboard-custom-navigation/src/NwindData.json b/samples/grids/grid/keyboard-navigation-guide/src/NwindData.json similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/src/NwindData.json rename to samples/grids/grid/keyboard-navigation-guide/src/NwindData.json diff --git a/samples/grids/grid/keyboard-custom-navigation/src/index.css b/samples/grids/grid/keyboard-navigation-guide/src/index.css similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/src/index.css rename to samples/grids/grid/keyboard-navigation-guide/src/index.css diff --git a/samples/grids/grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/grid/keyboard-navigation-guide/src/index.tsx similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/src/index.tsx rename to samples/grids/grid/keyboard-navigation-guide/src/index.tsx diff --git a/samples/grids/grid/keyboard-custom-navigation/src/react-app-env.d.ts b/samples/grids/grid/keyboard-navigation-guide/src/react-app-env.d.ts similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/src/react-app-env.d.ts rename to samples/grids/grid/keyboard-navigation-guide/src/react-app-env.d.ts diff --git a/samples/grids/grid/keyboard-custom-navigation/tsconfig.json b/samples/grids/grid/keyboard-navigation-guide/tsconfig.json similarity index 100% rename from samples/grids/grid/keyboard-custom-navigation/tsconfig.json rename to samples/grids/grid/keyboard-navigation-guide/tsconfig.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/.eslintrc.js b/samples/grids/hierarchical-grid/keyboard-navigation-guide/.eslintrc.js similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/.eslintrc.js rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/.eslintrc.js diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/ReadMe.md b/samples/grids/hierarchical-grid/keyboard-navigation-guide/ReadMe.md similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/ReadMe.md rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/ReadMe.md diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/package.json b/samples/grids/hierarchical-grid/keyboard-navigation-guide/package.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/package.json rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/package.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/public/index.html b/samples/grids/hierarchical-grid/keyboard-navigation-guide/public/index.html similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/public/index.html rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/public/index.html diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/sandbox.config.json b/samples/grids/hierarchical-grid/keyboard-navigation-guide/sandbox.config.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/sandbox.config.json rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/sandbox.config.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/NwindData.json b/samples/grids/hierarchical-grid/keyboard-navigation-guide/src/NwindData.json similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/src/NwindData.json rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/src/NwindData.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/SingersData.json b/samples/grids/hierarchical-grid/keyboard-navigation-guide/src/SingersData.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/src/SingersData.json rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/src/SingersData.json diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.css b/samples/grids/hierarchical-grid/keyboard-navigation-guide/src/index.css similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.css rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/src/index.css diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/hierarchical-grid/keyboard-navigation-guide/src/index.tsx similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/src/index.tsx rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/src/index.tsx diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/src/react-app-env.d.ts b/samples/grids/hierarchical-grid/keyboard-navigation-guide/src/react-app-env.d.ts similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/src/react-app-env.d.ts rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/src/react-app-env.d.ts diff --git a/samples/grids/hierarchical-grid/keyboard-custom-navigation/tsconfig.json b/samples/grids/hierarchical-grid/keyboard-navigation-guide/tsconfig.json similarity index 100% rename from samples/grids/hierarchical-grid/keyboard-custom-navigation/tsconfig.json rename to samples/grids/hierarchical-grid/keyboard-navigation-guide/tsconfig.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/.eslintrc.js b/samples/grids/tree-grid/keyboard-navigation-guide/.eslintrc.js similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/.eslintrc.js rename to samples/grids/tree-grid/keyboard-navigation-guide/.eslintrc.js diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/ReadMe.md b/samples/grids/tree-grid/keyboard-navigation-guide/ReadMe.md similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/ReadMe.md rename to samples/grids/tree-grid/keyboard-navigation-guide/ReadMe.md diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/package.json b/samples/grids/tree-grid/keyboard-navigation-guide/package.json similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/package.json rename to samples/grids/tree-grid/keyboard-navigation-guide/package.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/public/index.html b/samples/grids/tree-grid/keyboard-navigation-guide/public/index.html similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/public/index.html rename to samples/grids/tree-grid/keyboard-navigation-guide/public/index.html diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/sandbox.config.json b/samples/grids/tree-grid/keyboard-navigation-guide/sandbox.config.json similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/sandbox.config.json rename to samples/grids/tree-grid/keyboard-navigation-guide/sandbox.config.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/EmployeesNestedData.ts b/samples/grids/tree-grid/keyboard-navigation-guide/src/EmployeesNestedData.ts similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/src/EmployeesNestedData.ts rename to samples/grids/tree-grid/keyboard-navigation-guide/src/EmployeesNestedData.ts diff --git a/samples/grids/tree-grid/keyboard-navigation-guide/src/NwindData.json b/samples/grids/tree-grid/keyboard-navigation-guide/src/NwindData.json new file mode 100644 index 0000000000..c00b03ec8d --- /dev/null +++ b/samples/grids/tree-grid/keyboard-navigation-guide/src/NwindData.json @@ -0,0 +1,458 @@ +[ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2012-02-12", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": true, + "OrderDate": "2003-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2006-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 4, + "ProductName": "Chef Antons Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2016-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 5, + "ProductName": "Chef Antons Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2011-11-11", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 6, + "ProductName": "Grandmas Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2017-12-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 7, + "ProductName": "Uncle Bobs Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 150, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2016-07-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2018-01-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2010-02-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2008-05-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": false, + "OrderDate": "2009-01-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2015-11-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2015-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2017-06-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2014-03-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + }, + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2018-03-28", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2015-08-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-09-27", + "Rating": 2, + "Locations": [ + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + }, + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2001-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + } + ] + }, + { + "ProductID": 20, + "ProductName": "Sir Rodneys Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "4 - 100 ml jars", + "UnitPrice": 4.5, + "UnitsInStock": 40, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + } +] \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css b/samples/grids/tree-grid/keyboard-navigation-guide/src/index.css similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/src/index.css rename to samples/grids/tree-grid/keyboard-navigation-guide/src/index.css diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/tree-grid/keyboard-navigation-guide/src/index.tsx similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx rename to samples/grids/tree-grid/keyboard-navigation-guide/src/index.tsx diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/react-app-env.d.ts b/samples/grids/tree-grid/keyboard-navigation-guide/src/react-app-env.d.ts similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/src/react-app-env.d.ts rename to samples/grids/tree-grid/keyboard-navigation-guide/src/react-app-env.d.ts diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/tsconfig.json b/samples/grids/tree-grid/keyboard-navigation-guide/tsconfig.json similarity index 100% rename from samples/grids/tree-grid/keyboard-custom-navigation/tsconfig.json rename to samples/grids/tree-grid/keyboard-navigation-guide/tsconfig.json From cc3c7b8e90b416e9cca593f53a82a5bc463496dc Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 15:37:10 +0300 Subject: [PATCH 5/6] fix(samples): re-added custom nav --- .../keyboard-custom-navigation/.eslintrc.js | 78 +++ .../grid/keyboard-custom-navigation/ReadMe.md | 56 +++ .../keyboard-custom-navigation/package.json | 48 ++ .../public/index.html | 11 + .../sandbox.config.json | 5 + .../src/NwindData.json | 458 ++++++++++++++++++ .../keyboard-custom-navigation/src/index.css | 7 + .../keyboard-custom-navigation/src/index.tsx | 131 +++++ .../src/react-app-env.d.ts | 1 + .../keyboard-custom-navigation/tsconfig.json | 44 ++ 10 files changed, 839 insertions(+) create mode 100644 samples/grids/grid/keyboard-custom-navigation/.eslintrc.js create mode 100644 samples/grids/grid/keyboard-custom-navigation/ReadMe.md create mode 100644 samples/grids/grid/keyboard-custom-navigation/package.json create mode 100644 samples/grids/grid/keyboard-custom-navigation/public/index.html create mode 100644 samples/grids/grid/keyboard-custom-navigation/sandbox.config.json create mode 100644 samples/grids/grid/keyboard-custom-navigation/src/NwindData.json create mode 100644 samples/grids/grid/keyboard-custom-navigation/src/index.css create mode 100644 samples/grids/grid/keyboard-custom-navigation/src/index.tsx create mode 100644 samples/grids/grid/keyboard-custom-navigation/src/react-app-env.d.ts create mode 100644 samples/grids/grid/keyboard-custom-navigation/tsconfig.json diff --git a/samples/grids/grid/keyboard-custom-navigation/.eslintrc.js b/samples/grids/grid/keyboard-custom-navigation/.eslintrc.js new file mode 100644 index 0000000000..7168b71441 --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/.eslintrc.js @@ -0,0 +1,78 @@ +// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project +module.exports = { + parser: "@typescript-eslint/parser", // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module", // Allows for the use of imports + ecmaFeatures: { + jsx: true // Allows for the parsing of JSX + } + }, + settings: { + react: { + version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use + } + }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react + "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-prototype-builtins": "off", + "no-mixed-spaces-and-tabs": 0, + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-var": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-mixed-spaces-and-tabs": 0, + "no-prototype-builtins": "off", + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } + } + ] + }; \ No newline at end of file diff --git a/samples/grids/grid/keyboard-custom-navigation/ReadMe.md b/samples/grids/grid/keyboard-custom-navigation/ReadMe.md new file mode 100644 index 0000000000..ed2f5e5fc6 --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/ReadMe.md @@ -0,0 +1,56 @@ + + + +This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## Branches + +> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository. + +## Instructions + +Follow these instructions to run this example: + + +``` +git clone https://github.com/IgniteUI/igniteui-react-examples.git +git checkout master +cd ./igniteui-react-examples +cd ./samples/grids/grid/keyboard-custom-navigation +``` + +open above folder in VS Code or type: +``` +code . +``` + +In terminal window, run: +``` +npm install --legacy-peer-deps +npm run-script start +``` + +Then open http://localhost:4200/ in your browser + + +## Learn More + +To learn more about **Ignite UI for React** components, check out the [React documentation](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html). diff --git a/samples/grids/grid/keyboard-custom-navigation/package.json b/samples/grids/grid/keyboard-custom-navigation/package.json new file mode 100644 index 0000000000..9246bcd77a --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/package.json @@ -0,0 +1,48 @@ +{ + "name": "example-ignite-ui-react", + "description": "This project provides example of using Ignite UI for React components", + "author": "Infragistics", + "version": "1.4.0", + "license": "", + "homepage": ".", + "private": true, + "scripts": { + "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start", + "build": "react-scripts --max_old_space_size=10240 build ", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject", + "lint": "eslint ./src/**/*.{ts,tsx}" + }, + "dependencies": { + "igniteui-dockmanager": "^1.17.0", + "igniteui-react": "^19.2.0", + "igniteui-react-core": "^19.0.1", + "igniteui-react-grids": "^19.2.0", + "igniteui-react-inputs": "^19.0.1", + "igniteui-react-layouts": "^19.0.1", + "igniteui-webcomponents": "^6.2.0", + "lit-html": "^3.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "^5.0.1", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@types/jest": "^29.2.0", + "@types/node": "^18.11.7", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "eslint": "^8.33.0", + "eslint-config-react": "^1.1.7", + "eslint-plugin-react": "^7.20.0", + "react-app-rewired": "^2.2.1", + "typescript": "^4.8.4", + "worker-loader": "^3.0.8" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/samples/grids/grid/keyboard-custom-navigation/public/index.html b/samples/grids/grid/keyboard-custom-navigation/public/index.html new file mode 100644 index 0000000000..e2d3265576 --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/public/index.html @@ -0,0 +1,11 @@ + + + + Sample | Ignite UI | React | infragistics + + + + +
+ + \ No newline at end of file diff --git a/samples/grids/grid/keyboard-custom-navigation/sandbox.config.json b/samples/grids/grid/keyboard-custom-navigation/sandbox.config.json new file mode 100644 index 0000000000..07f53508eb --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/sandbox.config.json @@ -0,0 +1,5 @@ +{ + "infiniteLoopProtection": false, + "hardReloadOnChange": false, + "view": "browser" +} \ No newline at end of file diff --git a/samples/grids/grid/keyboard-custom-navigation/src/NwindData.json b/samples/grids/grid/keyboard-custom-navigation/src/NwindData.json new file mode 100644 index 0000000000..c00b03ec8d --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/src/NwindData.json @@ -0,0 +1,458 @@ +[ + { + "ProductID": 1, + "ProductName": "Chai", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "10 boxes x 20 bags", + "UnitPrice": 18, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2012-02-12", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 2, + "ProductName": "Chang", + "SupplierID": 1, + "CategoryID": 1, + "QuantityPerUnit": "24 - 12 oz bottles", + "UnitPrice": 19, + "UnitsInStock": 17, + "UnitsOnOrder": 40, + "ReorderLevel": 25, + "Discontinued": true, + "OrderDate": "2003-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 3, + "ProductName": "Aniseed Syrup", + "SupplierID": 1, + "CategoryID": 2, + "QuantityPerUnit": "12 - 550 ml bottles", + "UnitPrice": 10, + "UnitsInStock": 13, + "UnitsOnOrder": 70, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2006-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 4, + "ProductName": "Chef Antons Cajun Seasoning", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "48 - 6 oz jars", + "UnitPrice": 22, + "UnitsInStock": 53, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2016-03-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 5, + "ProductName": "Chef Antons Gumbo Mix", + "SupplierID": 2, + "CategoryID": 2, + "QuantityPerUnit": "36 boxes", + "UnitPrice": 21.35, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2011-11-11", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 6, + "ProductName": "Grandmas Boysenberry Spread", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 8 oz jars", + "UnitPrice": 25, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 25, + "Discontinued": false, + "OrderDate": "2017-12-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 7, + "ProductName": "Uncle Bobs Organic Dried Pears", + "SupplierID": 3, + "CategoryID": 7, + "QuantityPerUnit": "12 - 1 lb pkgs.", + "UnitPrice": 30, + "UnitsInStock": 150, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2016-07-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 8, + "ProductName": "Northwoods Cranberry Sauce", + "SupplierID": 3, + "CategoryID": 2, + "QuantityPerUnit": "12 - 12 oz jars", + "UnitPrice": 40, + "UnitsInStock": 6, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2018-01-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 9, + "ProductName": "Mishi Kobe Niku", + "SupplierID": 4, + "CategoryID": 6, + "QuantityPerUnit": "18 - 500 g pkgs.", + "UnitPrice": 97, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2010-02-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 10, + "ProductName": "Ikura", + "SupplierID": 4, + "CategoryID": 8, + "QuantityPerUnit": "12 - 200 ml jars", + "UnitPrice": 31, + "UnitsInStock": 31, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2008-05-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 11, + "ProductName": "Queso Cabrales", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "1 kg pkg.", + "UnitPrice": 21, + "UnitsInStock": 22, + "UnitsOnOrder": 30, + "ReorderLevel": 30, + "Discontinued": false, + "OrderDate": "2009-01-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Fun-Tasty Co.", + "LastInventory": "2018-06-12" + }, + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 12, + "ProductName": "Queso Manchego La Pastora", + "SupplierID": 5, + "CategoryID": 4, + "QuantityPerUnit": "10 - 500 g pkgs.", + "UnitPrice": 38, + "UnitsInStock": 86, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2015-11-17", + "Rating": 3, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 13, + "ProductName": "Konbu", + "SupplierID": 6, + "CategoryID": 8, + "QuantityPerUnit": "2 kg box", + "UnitPrice": 6, + "UnitsInStock": 24, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2015-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 14, + "ProductName": "Tofu", + "SupplierID": 6, + "CategoryID": 7, + "QuantityPerUnit": "40 - 100 g pkgs.", + "UnitPrice": 23.25, + "UnitsInStock": 35, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2017-06-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + } + ] + }, + { + "ProductID": 15, + "ProductName": "Genen Shouyu", + "SupplierID": 6, + "CategoryID": 2, + "QuantityPerUnit": "24 - 250 ml bottles", + "UnitPrice": 15.5, + "UnitsInStock": 39, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2014-03-17", + "Rating": 4, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + }, + { + "Shop": "Wall Market", + "LastInventory": "2018-12-06" + } + ] + }, + { + "ProductID": 16, + "ProductName": "Pavlova", + "SupplierID": 7, + "CategoryID": 3, + "QuantityPerUnit": "32 - 500 g boxes", + "UnitPrice": 17.45, + "UnitsInStock": 29, + "UnitsOnOrder": 30, + "ReorderLevel": 10, + "Discontinued": false, + "OrderDate": "2018-03-28", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + }, + { + "Shop": "Street Market", + "LastInventory": "2018-12-12" + }, + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + } + ] + }, + { + "ProductID": 17, + "ProductName": "Alice Mutton", + "SupplierID": 7, + "CategoryID": 6, + "QuantityPerUnit": "20 - 1 kg tins", + "UnitPrice": 39, + "UnitsInStock": 0, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": true, + "OrderDate": "2015-08-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Farmer Market", + "LastInventory": "2018-04-04" + } + ] + }, + { + "ProductID": 18, + "ProductName": "Carnarvon Tigers", + "SupplierID": 7, + "CategoryID": 8, + "QuantityPerUnit": "16 kg pkg.", + "UnitPrice": 62.5, + "UnitsInStock": 42, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-09-27", + "Rating": 2, + "Locations": [ + { + "Shop": "24/7 Market", + "LastInventory": "2018-11-11" + }, + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + }, + { + "ProductID": 19, + "ProductName": "Teatime Chocolate Biscuits", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "", + "UnitPrice": 9.2, + "UnitsInStock": 25, + "UnitsOnOrder": 30, + "ReorderLevel": 5, + "Discontinued": false, + "OrderDate": "2001-03-17", + "Rating": 2, + "Locations": [ + { + "Shop": "Local Market", + "LastInventory": "2018-07-03" + } + ] + }, + { + "ProductID": 20, + "ProductName": "Sir Rodneys Marmalade", + "SupplierID": 8, + "CategoryID": 3, + "QuantityPerUnit": "4 - 100 ml jars", + "UnitPrice": 4.5, + "UnitsInStock": 40, + "UnitsOnOrder": 30, + "ReorderLevel": 0, + "Discontinued": false, + "OrderDate": "2005-03-17", + "Rating": 5, + "Locations": [ + { + "Shop": "Super Market", + "LastInventory": "2018-09-09" + } + ] + } +] \ No newline at end of file diff --git a/samples/grids/grid/keyboard-custom-navigation/src/index.css b/samples/grids/grid/keyboard-custom-navigation/src/index.css new file mode 100644 index 0000000000..3b330b391d --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/src/index.css @@ -0,0 +1,7 @@ +/* shared styles are loaded from: */ +/* https://static.infragistics.com/xplatform/css/samples */ + + #grid { + --ig-size: var(--ig-size-medium); + } + diff --git a/samples/grids/grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/grid/keyboard-custom-navigation/src/index.tsx new file mode 100644 index 0000000000..5988213a66 --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/src/index.tsx @@ -0,0 +1,131 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; + +import { IgrPropertyEditorPanelModule } from 'igniteui-react-layouts'; +import { IgrGridModule } from 'igniteui-react-grids'; +import { IgrGrid, IgrColumn } from 'igniteui-react-grids'; +import { ComponentRenderer, PropertyEditorPanelDescriptionModule, WebGridDescriptionModule } from 'igniteui-react-core'; +import NwindData from './NwindData.json'; +import { IgrGridKeydownEventArgs } from 'igniteui-react-grids'; + +import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; + +const mods: any[] = [ + IgrPropertyEditorPanelModule, + IgrGridModule +]; +mods.forEach((m) => m.register()); + +export default class Sample extends React.Component { + private grid: IgrGrid + private gridRef(r: IgrGrid) { + this.grid = r; + this.setState({}); + } + + constructor(props: any) { + super(props); + + this.gridRef = this.gridRef.bind(this); + this.webGridCustomKBNav = this.webGridCustomKBNav.bind(this); + } + + public render(): JSX.Element { + return ( +
+ +
+ + + + + + + + + + + + + + +
+
+ ); + } + + private _nwindData: any[] = NwindData; + public get nwindData(): any[] { + return this._nwindData; + } + + private _componentRenderer: ComponentRenderer = null; + public get renderer(): ComponentRenderer { + if (this._componentRenderer == null) { + this._componentRenderer = new ComponentRenderer(); + var context = this._componentRenderer.context; + PropertyEditorPanelDescriptionModule.register(context); + WebGridDescriptionModule.register(context); + } + return this._componentRenderer; + } + + public webGridCustomKBNav(eventArgs: IgrGridKeydownEventArgs): void { + const args = eventArgs.detail; + const target = args.target; + const evt = args.event; + const type = args.targetType; + const grid = eventArgs.target as IgrGrid; + + if (type === 'dataCell' && target.editMode && evt.key.toLowerCase() === 'tab') { + // Value validation for number column. + // This covers both 'tab' and 'shift+tab' key interactions. + args.event.preventDefault(); + args.cancel = true; + if (target.column.dataType === 'number' && target.editValue < 10) { + alert('The value should be bigger than 10'); + return; + } + const cell = evt.shiftKey ? + grid.getPreviousCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable) : + grid.getNextCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable); + + grid.navigateTo(cell.rowIndex, cell.visibleColumnIndex, + (obj: any) => { obj.target.activate(); }); + } else if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') { + // Perform column based kb navigation with 'enter' key press + args.cancel = true; + grid.navigateTo(target.row.index + 1, target.column.visibleIndex, (obj: any) => { + obj.target.activate(); + }); + } + } + +} + +// rendering above component in the React DOM +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(); \ No newline at end of file diff --git a/samples/grids/grid/keyboard-custom-navigation/src/react-app-env.d.ts b/samples/grids/grid/keyboard-custom-navigation/src/react-app-env.d.ts new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/samples/grids/grid/keyboard-custom-navigation/tsconfig.json b/samples/grids/grid/keyboard-custom-navigation/tsconfig.json new file mode 100644 index 0000000000..8c0d146f95 --- /dev/null +++ b/samples/grids/grid/keyboard-custom-navigation/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true, + "baseUrl": ".", + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "noUnusedLocals": false, + "importHelpers": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": false, + "isolatedModules": true, + "noEmit": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts", + "**/odatajs-4.0.0.js", + "config-overrides.js" + ], + "include": [ + "src" + ] +} From 785e8539928c206ea43f0dc430084331c7ae8945 Mon Sep 17 00:00:00 2001 From: tishko0 Date: Mon, 6 Oct 2025 16:54:33 +0300 Subject: [PATCH 6/6] fix(sample): re-added tgrid custom keynav sample --- .../keyboard-custom-navigation/.eslintrc.js | 78 ++++++++++ .../keyboard-custom-navigation/ReadMe.md | 56 +++++++ .../keyboard-custom-navigation/package.json | 48 ++++++ .../public/index.html | 11 ++ .../sandbox.config.json | 5 + .../src/EmployeesNestedData.ts | 75 ++++++++++ .../keyboard-custom-navigation/src/index.css | 2 + .../keyboard-custom-navigation/src/index.tsx | 137 ++++++++++++++++++ .../src/react-app-env.d.ts | 1 + .../keyboard-custom-navigation/tsconfig.json | 44 ++++++ 10 files changed, 457 insertions(+) create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/.eslintrc.js create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/ReadMe.md create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/package.json create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/public/index.html create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/sandbox.config.json create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/src/EmployeesNestedData.ts create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/src/index.css create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/src/react-app-env.d.ts create mode 100644 samples/grids/tree-grid/keyboard-custom-navigation/tsconfig.json diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/.eslintrc.js b/samples/grids/tree-grid/keyboard-custom-navigation/.eslintrc.js new file mode 100644 index 0000000000..7168b71441 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/.eslintrc.js @@ -0,0 +1,78 @@ +// https://www.robertcooper.me/using-eslint-and-prettier-in-a-typescript-project +module.exports = { + parser: "@typescript-eslint/parser", // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: "module", // Allows for the use of imports + ecmaFeatures: { + jsx: true // Allows for the parsing of JSX + } + }, + settings: { + react: { + version: "999.999.999" // Tells eslint-plugin-react to automatically detect the version of React to use + } + }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react + "plugin:@typescript-eslint/recommended" // Uses the recommended rules from @typescript-eslint/eslint-plugin + ], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-prototype-builtins": "off", + "no-mixed-spaces-and-tabs": 0, + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + }, + "overrides": [ + { + "files": ["*.ts", "*.tsx"], + "rules": { + "default-case": "off", + "jsx-a11y/alt-text": "off", + "jsx-a11y/iframe-has-title": "off", + "no-var": "off", + "no-undef": "off", + "no-unused-vars": "off", + "no-extend-native": "off", + "no-throw-literal": "off", + "no-useless-concat": "off", + "no-mixed-operators": "off", + "no-mixed-spaces-and-tabs": 0, + "no-prototype-builtins": "off", + "prefer-const": "off", + "prefer-rest-params": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/prefer-namespace-keyword": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off" + } + } + ] + }; \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/ReadMe.md b/samples/grids/tree-grid/keyboard-custom-navigation/ReadMe.md new file mode 100644 index 0000000000..e7047da5aa --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/ReadMe.md @@ -0,0 +1,56 @@ + + + +This folder contains implementation of React application with example of Keyboard Custom Navigation feature using [Tree Grid](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html) component. + + + + + + View Docs + + + View Code + + + Run Sample + + + Run Sample + + + + +## Branches + +> **_NOTE:_** You should use [master](https://github.com/IgniteUI/igniteui-react-examples/tree/master) branch of this repository if you want to run samples on your computer. Use the [vnext](https://github.com/IgniteUI/igniteui-react-examples/tree/vnext) branch only when you want to contribute new samples to this repository. + +## Instructions + +Follow these instructions to run this example: + + +``` +git clone https://github.com/IgniteUI/igniteui-react-examples.git +git checkout master +cd ./igniteui-react-examples +cd ./samples/grids/tree-grid/keyboard-custom-navigation +``` + +open above folder in VS Code or type: +``` +code . +``` + +In terminal window, run: +``` +npm install --legacy-peer-deps +npm run-script start +``` + +Then open http://localhost:4200/ in your browser + + +## Learn More + +To learn more about **Ignite UI for React** components, check out the [React documentation](https://www.infragistics.com/products/ignite-ui-react/react/components/general-getting-started.html). diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/package.json b/samples/grids/tree-grid/keyboard-custom-navigation/package.json new file mode 100644 index 0000000000..9246bcd77a --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/package.json @@ -0,0 +1,48 @@ +{ + "name": "example-ignite-ui-react", + "description": "This project provides example of using Ignite UI for React components", + "author": "Infragistics", + "version": "1.4.0", + "license": "", + "homepage": ".", + "private": true, + "scripts": { + "start": "set PORT=4200 && react-scripts --max_old_space_size=10240 start", + "build": "react-scripts --max_old_space_size=10240 build ", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject", + "lint": "eslint ./src/**/*.{ts,tsx}" + }, + "dependencies": { + "igniteui-dockmanager": "^1.17.0", + "igniteui-react": "^19.2.0", + "igniteui-react-core": "^19.0.1", + "igniteui-react-grids": "^19.2.0", + "igniteui-react-inputs": "^19.0.1", + "igniteui-react-layouts": "^19.0.1", + "igniteui-webcomponents": "^6.2.0", + "lit-html": "^3.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "^5.0.1", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@types/jest": "^29.2.0", + "@types/node": "^18.11.7", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "eslint": "^8.33.0", + "eslint-config-react": "^1.1.7", + "eslint-plugin-react": "^7.20.0", + "react-app-rewired": "^2.2.1", + "typescript": "^4.8.4", + "worker-loader": "^3.0.8" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/public/index.html b/samples/grids/tree-grid/keyboard-custom-navigation/public/index.html new file mode 100644 index 0000000000..e2d3265576 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/public/index.html @@ -0,0 +1,11 @@ + + + + Sample | Ignite UI | React | infragistics + + + + +
+ + \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/sandbox.config.json b/samples/grids/tree-grid/keyboard-custom-navigation/sandbox.config.json new file mode 100644 index 0000000000..07f53508eb --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/sandbox.config.json @@ -0,0 +1,5 @@ +{ + "infiniteLoopProtection": false, + "hardReloadOnChange": false, + "view": "browser" +} \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/EmployeesNestedData.ts b/samples/grids/tree-grid/keyboard-custom-navigation/src/EmployeesNestedData.ts new file mode 100644 index 0000000000..0728ee874d --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/src/EmployeesNestedData.ts @@ -0,0 +1,75 @@ +export class EmployeesNestedDataItem { + public constructor(init: Partial) { + Object.assign(this, init); + } + + public ID: number; + public Age: number; + public Salary: number; + public Productivity: number; + public City: string; + public Country: string; + public Phone: string; + public HireDate: string; + public Name: string; + public Title: string; + public Employees: EmployeesNestedDataItem_EmployeesItem[]; + +} +export class EmployeesNestedDataItem_EmployeesItem { + public constructor(init: Partial) { + Object.assign(this, init); + } + + public Age: number; + public Salary: number; + public Productivity: number; + public City: string; + public Country: string; + public Phone: string; + public HireDate: string; + public ID: number; + public Name: string; + public Title: string; + +} +export class EmployeesNestedData extends Array { + public constructor(items: Array | number = -1) { + if (Array.isArray(items)) { + super(...items); + } else { + const newItems = [ + new EmployeesNestedDataItem({ ID: 1, Age: 55, Salary: 80000, Productivity: 90, City: `Berlin`, Country: `Germany`, Phone: `609-202-505`, HireDate: `2008-03-20`, Name: `John Winchester`, Title: `Development Manager`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] + }), + new EmployeesNestedDataItem({ ID: 4, Age: 42, Salary: 90000, Productivity: 80, City: `Kielce`, Country: `Poland`, Phone: `609-202-505`, HireDate: `2014-01-22`, Name: `Ana Sanders`, Title: `CEO`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 44, Salary: 80000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-202-505`, HireDate: `2014-04-04`, ID: 14, Name: `Laurence Johnson`, Title: `Director` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 25, Salary: 85000, Productivity: 55, City: `Paris`, Country: `France`, Phone: `609-202-505`, HireDate: `2017-11-09`, ID: 5, Name: `Elizabeth Richards`, Title: `Vice President` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 39, Salary: 88000, Productivity: 88, City: `London`, Country: `UK`, Phone: `609-202-505`, HireDate: `2010-03-22`, ID: 13, Name: `Trevor Ashworth`, Title: `Director` })] + }), + new EmployeesNestedDataItem({ ID: 18, Age: 49, Salary: 77000, Productivity: 70, City: `Manchester`, Country: `UK`, Phone: `222-555-577`, HireDate: `2014-01-22`, Name: `Victoria Lincoln`, Title: `Senior Accountant`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 23, Name: `Thomas Burke`, Title: `Senior Accountant` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 22, Name: `Michael Anderson`, Title: `Junior Accountant` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 21, Name: `Roland Reyes`, Title: `Accountant Team Lead` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 24, Name: `Monica Mendel`, Title: `Senior Software Developer` })] + }), + new EmployeesNestedDataItem({ ID: 10, Age: 61, Salary: 85000, Productivity: 890, City: `Lyon`, Country: `France`, Phone: `259-266-887`, HireDate: `2010-01-01`, Name: `Yang Wang`, Title: `Localization Developer`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] + }), + new EmployeesNestedDataItem({ ID: 35, Age: 35, Salary: 75000, Productivity: 75, City: `Warasw`, Country: `Poland`, Phone: `688-244-844`, HireDate: `2014-01-22`, Name: `Janine Munoz`, Title: `HR`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 43, Salary: 70000, Productivity: 80, City: `Hamburg`, Country: `Germany`, Phone: `609-444-555`, HireDate: `2011-06-03`, ID: 3, Name: `Michael Burke`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 31, Salary: 90000, Productivity: 80, City: `Warasw`, Country: `Poland`, Phone: `609-222-205`, HireDate: `2014-08-18`, ID: 11, Name: `Monica Reyes`, Title: `Software Development Team Lead` })] + }), + new EmployeesNestedDataItem({ ID: 10, Age: 49, Salary: 95000, Productivity: 80, City: `Krakow`, Country: `Poland`, Phone: `677-266-555`, HireDate: `2010-01-01`, Name: `Yang Wang`, Title: `Sales Manager`, Employees: [ + new EmployeesNestedDataItem_EmployeesItem({ Age: 29, Salary: 60000, Productivity: 80, City: `Munich`, Country: `Germany`, Phone: `609-333-444`, HireDate: `2009-06-19`, ID: 2, Name: `Thomas Anderson`, Title: `Senior Software Developer` }), + new EmployeesNestedDataItem_EmployeesItem({ Age: 35, Salary: 70000, Productivity: 70, City: `Koln`, Country: `Germany`, Phone: `609-502-525`, HireDate: `2015-09-17`, ID: 6, Name: `Roland Mendel`, Title: `Senior Software Developer` })] + }), + ]; + super(...newItems.slice(0)); + } + } +} diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css new file mode 100644 index 0000000000..98682b8543 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.css @@ -0,0 +1,2 @@ +/* shared styles are loaded from: */ +/* https://static.infragistics.com/xplatform/css/samples */ diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx new file mode 100644 index 0000000000..9488b0587c --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/src/index.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; + +import { IgrPropertyEditorPanelModule } from 'igniteui-react-layouts'; +import { IgrTreeGridModule } from 'igniteui-react-grids'; +import { IgrTreeGrid, IgrPaginator, IgrColumn } from 'igniteui-react-grids'; +import { ComponentRenderer, PropertyEditorPanelDescriptionModule, WebTreeGridDescriptionModule } from 'igniteui-react-core'; +import { EmployeesNestedDataItem, EmployeesNestedDataItem_EmployeesItem, EmployeesNestedData } from './EmployeesNestedData'; +import { IgrGrid, IgrGridKeydownEventArgs } from 'igniteui-react-grids'; + +import 'igniteui-react-grids/grids/themes/light/bootstrap.css'; + +const mods: any[] = [ + IgrPropertyEditorPanelModule, + IgrTreeGridModule +]; +mods.forEach((m) => m.register()); + +export default class Sample extends React.Component { + private treeGrid: IgrTreeGrid + private treeGridRef(r: IgrTreeGrid) { + this.treeGrid = r; + this.setState({}); + } + + constructor(props: any) { + super(props); + + this.treeGridRef = this.treeGridRef.bind(this); + this.webGridCustomKBNav = this.webGridCustomKBNav.bind(this); + } + + public render(): JSX.Element { + return ( +
+ +
+ + + + + + + + + + +
+
+ ); + } + + private _employeesNestedData: EmployeesNestedData = null; + public get employeesNestedData(): EmployeesNestedData { + if (this._employeesNestedData == null) + { + this._employeesNestedData = new EmployeesNestedData(); + } + return this._employeesNestedData; + } + + private _componentRenderer: ComponentRenderer = null; + public get renderer(): ComponentRenderer { + if (this._componentRenderer == null) { + this._componentRenderer = new ComponentRenderer(); + var context = this._componentRenderer.context; + PropertyEditorPanelDescriptionModule.register(context); + WebTreeGridDescriptionModule.register(context); + } + return this._componentRenderer; + } + + public webGridCustomKBNav(eventArgs: IgrGridKeydownEventArgs): void { + const args = eventArgs.detail; + const target = args.target; + const evt = args.event; + const type = args.targetType; + const grid = eventArgs.target as IgrGrid; + + if (type === 'dataCell' && target.editMode && evt.key.toLowerCase() === 'tab') { + // Value validation for number column. + // This covers both 'tab' and 'shift+tab' key interactions. + args.event.preventDefault(); + args.cancel = true; + if (target.column.dataType === 'number' && target.editValue < 10) { + alert('The value should be bigger than 10'); + return; + } + const cell = evt.shiftKey ? + grid.getPreviousCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable) : + grid.getNextCell(target.row.index, target.column.visibleIndex, (col: any) => col.editable); + + grid.navigateTo(cell.rowIndex, cell.visibleColumnIndex, + (obj: any) => { obj.target.activate(); }); + } else if (type === 'dataCell' && evt.key.toLowerCase() === 'enter') { + // Perform column based kb navigation with 'enter' key press + args.cancel = true; + grid.navigateTo(target.row.index + 1, target.column.visibleIndex, (obj: any) => { + obj.target.activate(); + }); + } + } + +} + +// rendering above component in the React DOM +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(); \ No newline at end of file diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/src/react-app-env.d.ts b/samples/grids/tree-grid/keyboard-custom-navigation/src/react-app-env.d.ts new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/samples/grids/tree-grid/keyboard-custom-navigation/tsconfig.json b/samples/grids/tree-grid/keyboard-custom-navigation/tsconfig.json new file mode 100644 index 0000000000..8c0d146f95 --- /dev/null +++ b/samples/grids/tree-grid/keyboard-custom-navigation/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true, + "baseUrl": ".", + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": [ + "es6", + "dom" + ], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "noUnusedLocals": false, + "importHelpers": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "strict": false, + "isolatedModules": true, + "noEmit": true + }, + "exclude": [ + "node_modules", + "build", + "scripts", + "acceptance-tests", + "webpack", + "jest", + "src/setupTests.ts", + "**/odatajs-4.0.0.js", + "config-overrides.js" + ], + "include": [ + "src" + ] +}