diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..c3b3d43a71 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,76 @@ +# GitHub Copilot Instructions for slickgrid-universal + +## Repository Overview +This is a monorepo for SlickGrid Universal containing: +- Core packages in `packages/` (common, plugins, exports, etc.) +- Framework wrappers in `frameworks/` (Angular, React, Vue, Aurelia) +- Demo applications in `demos/` for each framework + - except Angular demos which are located in `frameworks/angular-slickgrid/src/demos` +- Shared test utilities in `test/` + +## Code Standards + +### Testing +- Use **Vitest** for unit tests (`.spec.ts` files) +- Use **Cypress** for E2E tests (`.cy.ts` files in `test/cypress/e2e/`) +- Aim for 100% statement coverage on core functionality +- Test files are usually in `__tests__/` subdirectory (or same folder if only 1-2 test files) +- Unit tests exist in 2 implementations: + - Native/vanilla implementation: all under `packages/` + - Angular-specific tests: under `frameworks/angular-slickgrid/` + +### TypeScript Patterns +- Use strict TypeScript with proper typing +- Prefer `interface` over `type` for object shapes +- Use `protected` for class methods that may be extended +- Follow existing naming conventions: `_privateField`, `publicMethod` + +### Code Formatting & Linting +- **OXLint** is used for linting TypeScript/JavaScript code +- **Prettier** handles code formatting automatically +- Code should be formatted before committing +- Run `pnpm lint:fix` to auto-fix linting issues +- Run `pnpm prettier:write` to format code +- Ensure no linting errors before creating pull requests + +### Plugin Development +- All plugins extend SlickGrid and use `SlickEventHandler` +- Use `BindingEventService` for DOM event binding/cleanup +- Always implement `init()`, `dispose()`, and `getOptions()`/`setOptions()` +- Plugins should support both grid options and column definition options + +### Framework Support +When making changes to demos or documentation: +- Update **all 4 frameworks**: Angular, React, Vue, Aurelia +- Maintain consistency across framework implementations +- Check for framework-specific syntax (e.g., Vue's template vs React's JSX) + +### Documentation +- Update corresponding docs in `docs/` AND framework-specific `frameworks/*/docs/` +- Use markdown with proper linking: `[file.ts](path/to/file.ts)` +- Include code examples that work across frameworks + +## Common Commands +- `pnpm build` - Build all packages + - aka "Build Everything" task, which builds all packages and frameworks in the monorepo +- `pnpm lint` - Run linter (OXLint) on all files +- `pnpm lint:fix` - Auto-fix linting issues +- `pnpm prettier:check` - Check code formatting +- `pnpm prettier:write` - Format code with Prettier +- `pnpm test` - Run Vitest unit tests +- `pnpm test:coverage` - Run tests with coverage +- `pnpm dev` - Start vanilla demo +- `pnpm dev:[framework]` - Start framework-specific demos + - e.g.: `dev:angular`, `dev:react`, `dev:vue`, `dev:aurelia` + +## Monorepo Structure +- Changes to `packages/*` affect all frameworks +- Framework wrappers depend on core packages +- Use relative imports within packages +- Avoid circular dependencies + +## Code Review Focus +- Maintain backward compatibility +- Consider impact across all 4 framework implementations +- Verify tests pass and coverage remains high +- Check that examples work in all framework demos diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6525d632b6..a656074633 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -25,20 +25,6 @@ enabled: false, }, }, - { - description: 'Prevent major updates for Vite 6 catalog', - matchDepTypes: ['pnpm.catalog.vite6'], - major: { - enabled: false, - }, - }, - { - description: 'Ignore Analog packages', - matchPackagePatterns: ['^@analogjs/'], - groupName: 'Analog packages', - allowedVersions: '2.2.2', - enabled: false, // This will completely disable updates for Analog packages - }, ], ignoreDeps: ['lee-dohm/no-response', 'node', 'pnpm', 'typescript'], schedule: ['every 4 weeks on friday'], diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml index 50ce2852df..0c2a443cde 100644 --- a/.github/workflows/pkg-pr-new.yml +++ b/.github/workflows/pkg-pr-new.yml @@ -28,4 +28,4 @@ jobs: run: pnpm build - name: Publish - run: pnpm dlx pkg-pr-new publish --no-template --pnpm './packages/*' './frameworks/*' + run: pnpm dlx pkg-pr-new publish --no-template --pnpm './packages/*' './frameworks/*' './frameworks-plugins/*' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc09f4b8e0..674f9acbd8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,9 +6,6 @@ on: dryrun: type: boolean description: Dry-Run - graduate: - type: boolean - description: Force Conventional Graduate tag: type: choice default: latest @@ -73,20 +70,19 @@ jobs: - name: Lerna Version (build query) shell: bash run: | - if ${{inputs.dryrun == true && inputs.graduate != true}} + if ${{inputs.dryrun == true && (inputs.tag == 'alpha' || inputs.tag == 'beta' || inputs.tag == 'next')}} + then + echo "LERNA_VERSION_TYPE=๐Ÿงช Dry-Run w/Prerelease" >> $GITHUB_ENV + echo "LERNA_VERSION_QUERY=lerna version --yes --conventional-prerelease --preid ${{ inputs.tag }} --dry-run" >> $GITHUB_ENV + elif ${{inputs.dryrun == true}} then echo "LERNA_VERSION_TYPE=๐Ÿงช Dry-Run" >> $GITHUB_ENV echo "LERNA_VERSION_QUERY=lerna version --yes --dry-run" >> $GITHUB_ENV - elif ${{inputs.dryrun == true && inputs.graduate == true}} - then - echo "LERNA_VERSION_TYPE=๐Ÿงช Dry-Run w/Graduate" >> $GITHUB_ENV - echo "LERNA_VERSION_QUERY=lerna version --yes --dry-run --conventional-graduate" >> $GITHUB_ENV - elif ${{inputs.dryrun != true && inputs.graduate == true}} - then - echo "LERNA_VERSION_TYPE=๐Ÿš€ Prod Version w/Graduate" >> $GITHUB_ENV - echo "LERNA_VERSION_QUERY=lerna version --yes --conventional-graduate" >> $GITHUB_ENV - elif ${{inputs.dryrun != true && inputs.graduate != true}} + elif ${{inputs.tag == 'alpha' || inputs.tag == 'beta' || inputs.tag == 'next'}} then + echo "LERNA_VERSION_TYPE=๐Ÿš€ Prod Version w/Prerelease" >> $GITHUB_ENV + echo "LERNA_VERSION_QUERY=lerna version --yes --conventional-prerelease --preid ${{ inputs.tag }}" >> $GITHUB_ENV + else echo "LERNA_VERSION_TYPE=๐Ÿš€ Prod Version" >> $GITHUB_ENV echo "LERNA_VERSION_QUERY=lerna version --yes" >> $GITHUB_ENV fi @@ -104,13 +100,36 @@ jobs: git config --global user.email "${{ github.actor }}@users.noreply.github.com" pnpm exec ${{ env.LERNA_VERSION_QUERY }} + - name: Lerna Publish (build query) + shell: bash + run: | + if ${{inputs.dryrun == true && (inputs.tag == 'alpha' || inputs.tag == 'beta' || inputs.tag == 'next')}} + then + echo "LERNA_PUBLISH_TYPE=๐Ÿงช Dry-Run w/Prerelease" >> $GITHUB_ENV + echo "LERNA_PUBLISH_QUERY=lerna publish from-package --force-publish --yes --pre-dist-tag ${{ inputs.tag }} --dry-run" >> $GITHUB_ENV + elif ${{inputs.dryrun == true}} + then + echo "LERNA_PUBLISH_TYPE=๐Ÿงช Dry-Run" >> $GITHUB_ENV + echo "LERNA_PUBLISH_QUERY=lerna publish from-package --force-publish --yes --dist-tag latest --dry-run" >> $GITHUB_ENV + elif [[ "${{ inputs.tag }}" == "alpha" || "${{ inputs.tag }}" == "beta" || "${{ inputs.tag }}" == "next" ]] + then + echo "LERNA_PUBLISH_TYPE=๐Ÿ“ฆ Publish w/Prerelease" >> $GITHUB_ENV + echo "LERNA_PUBLISH_QUERY=lerna publish from-package --force-publish --yes --pre-dist-tag ${{ inputs.tag }}" >> $GITHUB_ENV + else + echo "LERNA_PUBLISH_TYPE=๐Ÿ“ฆ Publish Latest" >> $GITHUB_ENV + echo "LERNA_PUBLISH_QUERY=lerna publish from-package --force-publish --yes --dist-tag latest" >> $GITHUB_ENV + fi + + - name: Final publish type โ†’ ${{ env.LERNA_PUBLISH_TYPE }} + shell: bash + run: echo "${{ env.LERNA_PUBLISH_QUERY }}" + - name: Lerna Publish ๐Ÿ“ฆ - if: ${{ inputs.dryrun != true }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash id: lerna-publish - run: pnpm exec lerna publish from-package --force-publish --yes --dist-tag ${{ inputs.tag || 'latest' }} + run: pnpm exec ${{ env.LERNA_PUBLISH_QUERY }} - name: Website Dev Build run: pnpm build:dev diff --git a/.github/workflows/test-angular.yml b/.github/workflows/test-angular.yml index efee84125a..fdedfc9c1d 100644 --- a/.github/workflows/test-angular.yml +++ b/.github/workflows/test-angular.yml @@ -70,6 +70,9 @@ jobs: - name: Angular-Slickgrid Framework Build run: pnpm angular:build:framework + - name: Angular-Slickgrid Framework-Plugins Build + run: pnpm angular:build:framework-plugins + - name: Website Dev Build (served for Cypress) run: pnpm angular:build:demo @@ -80,7 +83,7 @@ jobs: run: pnpm angular:serve & - name: Run Cypress E2E tests - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@v7 with: install: false package-manager-cache: false diff --git a/.github/workflows/test-aurelia.yml b/.github/workflows/test-aurelia.yml index b7089615bd..f8d83ee847 100644 --- a/.github/workflows/test-aurelia.yml +++ b/.github/workflows/test-aurelia.yml @@ -55,6 +55,9 @@ jobs: - name: Aurelia-Slickgrid Framework Build run: pnpm aurelia:build:framework + - name: Aurelia-Slickgrid Framework-Plugins Build + run: pnpm aurelia:build:framework-plugins + - name: Website Dev Build (served for Cypress) run: pnpm aurelia:build:demo @@ -65,7 +68,7 @@ jobs: run: pnpm aurelia:serve & - name: Run Cypress E2E tests - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@v7 with: install: false package-manager-cache: false diff --git a/.github/workflows/test-react.yml b/.github/workflows/test-react.yml index e7e3565f4b..3838b22532 100644 --- a/.github/workflows/test-react.yml +++ b/.github/workflows/test-react.yml @@ -55,6 +55,9 @@ jobs: - name: Slickgrid-React Framework Build run: pnpm react:build:framework + - name: Slickgrid-React Framework-Plugins Build + run: pnpm react:build:framework-plugins + - name: Website Dev Build (served for Cypress) run: pnpm react:build:demo @@ -65,7 +68,7 @@ jobs: run: pnpm react:serve & - name: Run Cypress E2E tests - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@v7 with: install: false package-manager-cache: false diff --git a/.github/workflows/test-vanilla.yml b/.github/workflows/test-vanilla.yml index 12993c2b81..4cb1fa2d2f 100644 --- a/.github/workflows/test-vanilla.yml +++ b/.github/workflows/test-vanilla.yml @@ -62,7 +62,7 @@ jobs: run: pnpm vanilla:serve:demo & - name: Run Cypress E2E tests - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@v7 with: install: false package-manager-cache: false diff --git a/.github/workflows/test-vue.yml b/.github/workflows/test-vue.yml index 628a06e269..237b038b44 100644 --- a/.github/workflows/test-vue.yml +++ b/.github/workflows/test-vue.yml @@ -55,6 +55,9 @@ jobs: - name: Slickgrid-Vue Framework Build run: pnpm vue:build:framework + - name: Slickgrid-Vue Framework-Plugins Build + run: pnpm vue:build:framework-plugins + - name: Website Dev Build (served for Cypress) run: pnpm vue:build:demo @@ -65,7 +68,7 @@ jobs: run: pnpm vue:serve & - name: Run Cypress E2E tests - uses: cypress-io/github-action@v6 + uses: cypress-io/github-action@v7 with: install: false package-manager-cache: false diff --git a/.prettierignore b/.prettierignore index a61667e120..8f569d1b18 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,6 @@ _variables.scss _variables-theme-material.scss _variables-theme-salesforce.scss -**/test/vitest-coverage/*.* \ No newline at end of file +**/test/vitest-coverage/*.* +demos/vue/src/router/index.ts +demos/react/src/examples/slickgrid/App.tsx \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a4f7944a7..57b4ac2042 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,7 @@ ], "typescript.format.semicolons": "insert", "vitest.rootConfig": "./test/vitest.config.mts", - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules\\typescript\\lib", + "prettier.prettierPath": "node_modules/prettier/index.cjs", + "prettier.configPath": ".prettierrc" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ff21c45f1d..a9b3aac12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,58 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [10.0.0-beta.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v10.0.0-beta.0) (2026-02-14) + +### โš  BREAKING CHANGES + +* **angular:** migrate Angular-Slickgrid to Zoneless (#2341) +* **angular:** migrate Angular-Slickgrid to Standalone Component (#2339) +* upgrade to Angular 21 (#2338) +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag (#2331) +* drop Cell/Row Selection Models & keep only HybridSelectionModel (#2330) +* remove all Deprecated code (#2302) +* drop OperatorType enums and keep only type literal (#2301) +* replacing multiple TypeScript `enum` with `type` to decrease build size (#2300) +* drop FieldType enums and keep only field type literal (#2299) +* rename `v-model:data` to `v-model:dataset` (#2298) +* make Row Detail plugin as optional in all framework wrappers (#2291) +* rewrite Grid Menu using CSS flexbox instead of width and calc() (#2282) +* switch to column `hidden` property and always keep all columns (#2281) + +### Features + +* add custom menu slot renderers ([#2375](https://github.com/ghiscoding/slickgrid-universal/issues/2375)) ([7ebbda5](https://github.com/ghiscoding/slickgrid-universal/commit/7ebbda58233bb5ce94b81b5b2b578af0ea6a068d)) - by @ghiscoding +* **angular:** migrate Angular-Slickgrid to Standalone Component ([#2339](https://github.com/ghiscoding/slickgrid-universal/issues/2339)) ([89e8e26](https://github.com/ghiscoding/slickgrid-universal/commit/89e8e261af4a747b9bfd274d3df0953b231edb97)) - by @ghiscoding +* **angular:** migrate Angular-Slickgrid to Zoneless ([#2341](https://github.com/ghiscoding/slickgrid-universal/issues/2341)) ([f55cbe2](https://github.com/ghiscoding/slickgrid-universal/commit/f55cbe28ce6999fb3933bc55372c22a84df99a15)) - by @ghiscoding +* auto-enabled external resources with their associated flags ([#2362](https://github.com/ghiscoding/slickgrid-universal/issues/2362)) ([16dd8a7](https://github.com/ghiscoding/slickgrid-universal/commit/16dd8a77dd5d136a5a99321f0fc4c50571fdb0c0)) - by @ghiscoding +* bind css classes to slickgrid container ([#2369](https://github.com/ghiscoding/slickgrid-universal/issues/2369)) ([a7cd4aa](https://github.com/ghiscoding/slickgrid-universal/commit/a7cd4aa6af3688ebf1ec09140856ea451a6789da)) - by @zewa666 +* drop Cell/Row Selection Models & keep only HybridSelectionModel ([#2330](https://github.com/ghiscoding/slickgrid-universal/issues/2330)) ([4398cf4](https://github.com/ghiscoding/slickgrid-universal/commit/4398cf42e03f6971b81db8cea4ed11138c0aa452)) - by @ghiscoding +* F2 activates cell editor, closes [#2351](https://github.com/ghiscoding/slickgrid-universal/issues/2351) ([#2370](https://github.com/ghiscoding/slickgrid-universal/issues/2370)) ([6e06f9d](https://github.com/ghiscoding/slickgrid-universal/commit/6e06f9dd3e0ff78650683b0641191b94f2a2aa96)) - by @zewa666 +* make Row Detail plugin as optional in all framework wrappers ([#2291](https://github.com/ghiscoding/slickgrid-universal/issues/2291)) ([fa1a14c](https://github.com/ghiscoding/slickgrid-universal/commit/fa1a14c16c987bfaf7725c46e8114b20ea5a505d)) - by @ghiscoding +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag ([#2331](https://github.com/ghiscoding/slickgrid-universal/issues/2331)) ([5be5e6a](https://github.com/ghiscoding/slickgrid-universal/commit/5be5e6a862ecd024cf43d404769d65c6c1dd335e)) - by @ghiscoding +* rename `v-model:data` to `v-model:dataset` ([#2298](https://github.com/ghiscoding/slickgrid-universal/issues/2298)) ([34f42f2](https://github.com/ghiscoding/slickgrid-universal/commit/34f42f2d14c9a1b39a2695c8a885ff2bee53d0b5)) - by @ghiscoding +* **SlickCompositeEditor:** migrate modal from div to dialog ([#2283](https://github.com/ghiscoding/slickgrid-universal/issues/2283)) ([371c2c6](https://github.com/ghiscoding/slickgrid-universal/commit/371c2c675728f51785485280237572ef5a584eeb)) - by @ghiscoding +* switch to column `hidden` property and always keep all columns ([#2281](https://github.com/ghiscoding/slickgrid-universal/issues/2281)) ([075c649](https://github.com/ghiscoding/slickgrid-universal/commit/075c64961cb7400500df46b792866d39fba2d9e0)) - by @ghiscoding +* **tooltip:** add global tooltip observation for non-grid elements ([#2371](https://github.com/ghiscoding/slickgrid-universal/issues/2371)) ([1bbc8de](https://github.com/ghiscoding/slickgrid-universal/commit/1bbc8de895e370843286eadd08574efc552ad8fd)) - by @ghiscoding +* upgrade to Angular 21 ([#2338](https://github.com/ghiscoding/slickgrid-universal/issues/2338)) ([012def2](https://github.com/ghiscoding/slickgrid-universal/commit/012def265a4e5cb0738ea211d073df129a2eecd9)) - by @ghiscoding + +### Bug Fixes + +* **deps:** update all non-major dependencies ([#2292](https://github.com/ghiscoding/slickgrid-universal/issues/2292)) ([21da957](https://github.com/ghiscoding/slickgrid-universal/commit/21da95702f44739a9f1b226e305c18a1fa64000f)) - by @renovate-bot +* **filters:** prevent tooltip triggers on Slider programmatic updates ([#2372](https://github.com/ghiscoding/slickgrid-universal/issues/2372)) ([5734d59](https://github.com/ghiscoding/slickgrid-universal/commit/5734d59a4acfd5c32571ee2f0839561966b1ee2c)) - by @ghiscoding +* HybridSelectionModelOption should all be optional ([#2293](https://github.com/ghiscoding/slickgrid-universal/issues/2293)) ([1b8f760](https://github.com/ghiscoding/slickgrid-universal/commit/1b8f76060493c43a62e956fb9bf5e0e2feaf4a1f)) - by @ghiscoding +* **plugin:** SlickCustomTooltip should work with parent+child tooltips ([#2374](https://github.com/ghiscoding/slickgrid-universal/issues/2374)) ([8af7f45](https://github.com/ghiscoding/slickgrid-universal/commit/8af7f45eb19f0a00da2f3de7c729504be7d043eb)) - by @ghiscoding +* pre-set value for editor validation on paste ([#2368](https://github.com/ghiscoding/slickgrid-universal/issues/2368)) ([2df5cc5](https://github.com/ghiscoding/slickgrid-universal/commit/2df5cc5d529bff41513b05ecb322efc64d234d64)) - by @zewa666 +* remove all Deprecated code ([#2302](https://github.com/ghiscoding/slickgrid-universal/issues/2302)) ([f42c46c](https://github.com/ghiscoding/slickgrid-universal/commit/f42c46cd1f05b5c72c62f552f124b5bfe776f8b0)) - by @ghiscoding +* **styling:** add missing row highlight assignment for Dark Mode ([6ae55de](https://github.com/ghiscoding/slickgrid-universal/commit/6ae55de071325e37123a4eb32bd57a0d00581a17)) - by @ghiscoding + +### Code Refactoring + +* drop FieldType enums and keep only field type literal ([#2299](https://github.com/ghiscoding/slickgrid-universal/issues/2299)) ([3858ed0](https://github.com/ghiscoding/slickgrid-universal/commit/3858ed06a9496ddfa94d769ce1d415b68e18c993)) - by @ghiscoding +* drop OperatorType enums and keep only type literal ([#2301](https://github.com/ghiscoding/slickgrid-universal/issues/2301)) ([5dd0807](https://github.com/ghiscoding/slickgrid-universal/commit/5dd08079460dc9af798ab29527997a6d4b31abdd)) - by @ghiscoding +* replacing multiple TypeScript `enum` with `type` to decrease build size ([#2300](https://github.com/ghiscoding/slickgrid-universal/issues/2300)) ([ea79395](https://github.com/ghiscoding/slickgrid-universal/commit/ea79395cf663b3abce8e43cf27ba6ffea7cfe113)) - by @ghiscoding +* rewrite Grid Menu using CSS flexbox instead of width and calc() ([#2282](https://github.com/ghiscoding/slickgrid-universal/issues/2282)) ([2f5141a](https://github.com/ghiscoding/slickgrid-universal/commit/2f5141a12a765510e30a7155a7bf714e3462cfc1)) - by @ghiscoding + ## [9.13.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v9.13.0) (2026-01-30) ### Features diff --git a/demos/aurelia/CHANGELOG.md b/demos/aurelia/CHANGELOG.md index 05e7c2bc4a..daa98ab2f1 100644 --- a/demos/aurelia/CHANGELOG.md +++ b/demos/aurelia/CHANGELOG.md @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [10.0.0-beta.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v10.0.0-beta.0) (2026-02-14) + +### โš  BREAKING CHANGES + +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag (#2331) +* drop Cell/Row Selection Models & keep only HybridSelectionModel (#2330) +* remove all Deprecated code (#2302) +* drop OperatorType enums and keep only type literal (#2301) +* replacing multiple TypeScript `enum` with `type` to decrease build size (#2300) +* make Row Detail plugin as optional in all framework wrappers (#2291) +* switch to column `hidden` property and always keep all columns (#2281) + +### Features + +* add custom menu slot renderers ([#2375](https://github.com/ghiscoding/slickgrid-universal/issues/2375)) ([7ebbda5](https://github.com/ghiscoding/slickgrid-universal/commit/7ebbda58233bb5ce94b81b5b2b578af0ea6a068d)) - by @ghiscoding +* auto-enabled external resources with their associated flags ([#2362](https://github.com/ghiscoding/slickgrid-universal/issues/2362)) ([16dd8a7](https://github.com/ghiscoding/slickgrid-universal/commit/16dd8a77dd5d136a5a99321f0fc4c50571fdb0c0)) - by @ghiscoding +* drop Cell/Row Selection Models & keep only HybridSelectionModel ([#2330](https://github.com/ghiscoding/slickgrid-universal/issues/2330)) ([4398cf4](https://github.com/ghiscoding/slickgrid-universal/commit/4398cf42e03f6971b81db8cea4ed11138c0aa452)) - by @ghiscoding +* make Row Detail plugin as optional in all framework wrappers ([#2291](https://github.com/ghiscoding/slickgrid-universal/issues/2291)) ([fa1a14c](https://github.com/ghiscoding/slickgrid-universal/commit/fa1a14c16c987bfaf7725c46e8114b20ea5a505d)) - by @ghiscoding +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag ([#2331](https://github.com/ghiscoding/slickgrid-universal/issues/2331)) ([5be5e6a](https://github.com/ghiscoding/slickgrid-universal/commit/5be5e6a862ecd024cf43d404769d65c6c1dd335e)) - by @ghiscoding +* switch to column `hidden` property and always keep all columns ([#2281](https://github.com/ghiscoding/slickgrid-universal/issues/2281)) ([075c649](https://github.com/ghiscoding/slickgrid-universal/commit/075c64961cb7400500df46b792866d39fba2d9e0)) - by @ghiscoding +* **tooltip:** add global tooltip observation for non-grid elements ([#2371](https://github.com/ghiscoding/slickgrid-universal/issues/2371)) ([1bbc8de](https://github.com/ghiscoding/slickgrid-universal/commit/1bbc8de895e370843286eadd08574efc552ad8fd)) - by @ghiscoding + +### Bug Fixes + +* **plugin:** SlickCustomTooltip should work with parent+child tooltips ([#2374](https://github.com/ghiscoding/slickgrid-universal/issues/2374)) ([8af7f45](https://github.com/ghiscoding/slickgrid-universal/commit/8af7f45eb19f0a00da2f3de7c729504be7d043eb)) - by @ghiscoding +* remove all Deprecated code ([#2302](https://github.com/ghiscoding/slickgrid-universal/issues/2302)) ([f42c46c](https://github.com/ghiscoding/slickgrid-universal/commit/f42c46cd1f05b5c72c62f552f124b5bfe776f8b0)) - by @ghiscoding + +### Code Refactoring + +* drop OperatorType enums and keep only type literal ([#2301](https://github.com/ghiscoding/slickgrid-universal/issues/2301)) ([5dd0807](https://github.com/ghiscoding/slickgrid-universal/commit/5dd08079460dc9af798ab29527997a6d4b31abdd)) - by @ghiscoding +* replacing multiple TypeScript `enum` with `type` to decrease build size ([#2300](https://github.com/ghiscoding/slickgrid-universal/issues/2300)) ([ea79395](https://github.com/ghiscoding/slickgrid-universal/commit/ea79395cf663b3abce8e43cf27ba6ffea7cfe113)) - by @ghiscoding + ## [9.13.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v9.13.0) (2026-01-30) ### Features diff --git a/demos/aurelia/package.json b/demos/aurelia/package.json index c684cf4ed8..441828e1cb 100644 --- a/demos/aurelia/package.json +++ b/demos/aurelia/package.json @@ -1,7 +1,7 @@ { "name": "aurelia-slickgrid-demo", "private": true, - "version": "9.13.0", + "version": "10.0.0-beta.0", "description": "Aurelia-Slickgrid demos", "keywords": [ "aurelia", @@ -31,6 +31,7 @@ "@fnando/sparkline": "catalog:", "@formkit/tempo": "catalog:", "@popperjs/core": "catalog:", + "@slickgrid-universal/aurelia-row-detail-plugin": "workspace:*", "@slickgrid-universal/common": "workspace:*", "@slickgrid-universal/composite-editor-component": "workspace:*", "@slickgrid-universal/custom-tooltip-plugin": "workspace:*", @@ -45,6 +46,7 @@ "aurelia-slickgrid": "workspace:*", "bootstrap": "catalog:", "i18next": "catalog:", + "jspdf": "catalog:", "rxjs": "catalog:" }, "devDependencies": { @@ -59,7 +61,7 @@ "sass": "catalog:", "tslib": "catalog:", "typescript": "catalog:", - "vite": "catalog:vite7", + "vite": "catalog:", "vite-plugin-sass-dts": "^1.3.35" } } diff --git a/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelEditor.ts b/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelEditor.ts index 008e259c40..1e291b379b 100644 --- a/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelEditor.ts +++ b/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelEditor.ts @@ -33,7 +33,7 @@ export class CustomAureliaViewModelEditor implements Editor { elmBindingContext?: IBindingContext; constructor(private args: any) { - this.grid = args && args.grid; + this.grid = args?.grid; this.init(); } diff --git a/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelFilter.ts b/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelFilter.ts index d44cc0d24f..f5b6ebbb3a 100644 --- a/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelFilter.ts +++ b/demos/aurelia/src/examples/slickgrid/custom-aureliaViewModelFilter.ts @@ -3,14 +3,13 @@ import type { ICustomElementController } from '@aurelia/runtime-html'; import { AureliaUtilService, emptyElement, - OperatorType, type Column, type ColumnFilter, type Filter, type FilterArguments, type FilterCallback, type GridOption, - type OperatorString, + type OperatorType, type SearchTerm, type SlickGrid, type ViewModelBindableInputData, @@ -23,7 +22,7 @@ export class CustomAureliaViewModelFilter implements Filter { searchTerms: SearchTerm[] = []; columnDef!: Column; callback!: FilterCallback; - operator: OperatorType | OperatorString = OperatorType.equal; + operator: OperatorType = 'EQ'; /** Aurelia ViewModel Reference */ vm?: { controller?: ICustomElementController } | null; diff --git a/demos/aurelia/src/examples/slickgrid/custom-inputFilter.ts b/demos/aurelia/src/examples/slickgrid/custom-inputFilter.ts index fad4c145b6..5972dfba15 100644 --- a/demos/aurelia/src/examples/slickgrid/custom-inputFilter.ts +++ b/demos/aurelia/src/examples/slickgrid/custom-inputFilter.ts @@ -1,13 +1,12 @@ import { emptyElement, - OperatorType, type Column, type ColumnFilter, type Filter, type FilterArguments, type FilterCallback, type GridOption, - type OperatorString, + type OperatorType, type SearchTerm, type SlickGrid, } from 'aurelia-slickgrid'; @@ -20,7 +19,7 @@ export class CustomInputFilter implements Filter { searchTerms: SearchTerm[] = []; columnDef!: Column; callback!: FilterCallback; - operator: OperatorType | OperatorString = OperatorType.equal; + operator: OperatorType = 'EQ'; /** Getter for the Filter Operator */ get columnFilter(): ColumnFilter { diff --git a/demos/aurelia/src/examples/slickgrid/example10.ts b/demos/aurelia/src/examples/slickgrid/example10.ts index 4aff989b69..8788d246e7 100644 --- a/demos/aurelia/src/examples/slickgrid/example10.ts +++ b/demos/aurelia/src/examples/slickgrid/example10.ts @@ -148,7 +148,7 @@ export class Example10 { this.gridOptions1 = { enableAutoResize: false, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, enableCheckboxSelector: true, enableFiltering: true, checkboxSelector: { @@ -160,7 +160,7 @@ export class Example10 { // selectableOverride: (row: number, dataContext: any, grid: SlickGrid) => (dataContext.id % 2 === 1) }, multiSelect: false, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: true, }, @@ -196,12 +196,12 @@ export class Example10 { hideInColumnTitleRow: true, applySelectOnAllPages: true, // when clicking "Select All", should we apply it to all pages (defaults to true) }, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, enableCheckboxSelector: true, - enableRowSelection: true, gridHeight: 255, gridWidth: 800, enablePagination: true, @@ -293,7 +293,7 @@ export class Example10 { } onGrid1SelectedRowsChanged(_e: Event, args: any) { - const grid = args && args.grid; + const grid = args?.grid; if (Array.isArray(args.rows)) { this.selectedTitle = args.rows.map((idx: number) => { const item = grid.getDataItem(idx); diff --git a/demos/aurelia/src/examples/slickgrid/example11.html b/demos/aurelia/src/examples/slickgrid/example11.html index 22c7ce799c..71309fb273 100644 --- a/demos/aurelia/src/examples/slickgrid/example11.html +++ b/demos/aurelia/src/examples/slickgrid/example11.html @@ -25,7 +25,7 @@

  • Adding an item, will always be showing as the 1st item in the grid because that is the best visual place to add it
  • Add/Update an item requires a valid Slickgrid Selection Model, you have 2 choices to deal with this:
  • Click on any of the buttons below to test this out
  • diff --git a/demos/aurelia/src/examples/slickgrid/example11.ts b/demos/aurelia/src/examples/slickgrid/example11.ts index 039b136ba1..ec160a7e25 100644 --- a/demos/aurelia/src/examples/slickgrid/example11.ts +++ b/demos/aurelia/src/examples/slickgrid/example11.ts @@ -134,7 +134,7 @@ export class Example11 { editable: true, enableColumnPicker: true, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, }; } diff --git a/demos/aurelia/src/examples/slickgrid/example12.ts b/demos/aurelia/src/examples/slickgrid/example12.ts index 3091b3ebc2..a03b844c10 100644 --- a/demos/aurelia/src/examples/slickgrid/example12.ts +++ b/demos/aurelia/src/examples/slickgrid/example12.ts @@ -4,7 +4,6 @@ import { TextExportService } from '@slickgrid-universal/text-export'; import { resolve } from 'aurelia'; // import { TOptions as I18NOptions } from 'i18next'; import { - DelimiterType, Filters, Formatters, type AureliaGridInstance, @@ -178,7 +177,7 @@ export class Example12 { hideInColumnTitleRow: true, }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, showCustomFooter: true, // display some metrics in the bottom custom footer customFooterOptions: { metricTexts: { @@ -292,7 +291,7 @@ export class Example12 { exportToFile(type = 'csv') { this.textExportService.exportToFile({ - delimiter: type === 'csv' ? DelimiterType.comma : DelimiterType.tab, + delimiter: type === 'csv' ? ',' : '\t', filename: 'myExport', format: type === 'csv' ? 'csv' : 'txt', }); diff --git a/demos/aurelia/src/examples/slickgrid/example14.html b/demos/aurelia/src/examples/slickgrid/example14.html index 2710620da4..d6d7b2de22 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.html +++ b/demos/aurelia/src/examples/slickgrid/example14.html @@ -24,8 +24,25 @@

    -

    Grid 1 (with Header Grouping & Colspan)

    - +

    + Grid 1 (with Header Grouping & Colspan) + +

    + +
    diff --git a/demos/aurelia/src/examples/slickgrid/example14.ts b/demos/aurelia/src/examples/slickgrid/example14.ts index 66642904ce..f7daa332e4 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.ts +++ b/demos/aurelia/src/examples/slickgrid/example14.ts @@ -4,6 +4,7 @@ import { type AureliaGridInstance, type Column, type GridOption, type ItemMetada import './example14.scss'; // provide custom CSS/SASS styling export class Example14 { + aureliaGrid1!: AureliaGridInstance; aureliaGrid2!: AureliaGridInstance; gridObj2: any; columnDefinitions1: Column[] = []; @@ -13,6 +14,7 @@ export class Example14 { dataset1: any[] = []; dataset2: any[] = []; hideSubTitle = false; + isColspanSpreading = false; constructor() { this.definedGrid1(); @@ -25,6 +27,10 @@ export class Example14 { this.dataset2 = this.getData(500); } + aureliaGridReady1(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid1 = aureliaGrid; + } + aureliaGridReady2(aureliaGrid: AureliaGridInstance) { this.aureliaGrid2 = aureliaGrid; this.gridObj2 = aureliaGrid.slickGrid; @@ -65,6 +71,7 @@ export class Example14 { gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, + spreadHiddenColspan: this.isColspanSpreading, }; } @@ -156,6 +163,13 @@ export class Example14 { }; } + spreadColspan() { + this.isColspanSpreading = !this.isColspanSpreading; + this.aureliaGrid1.slickGrid?.setOptions({ spreadHiddenColspan: this.isColspanSpreading }); + this.aureliaGrid1.slickGrid?.resetActiveCell(); + this.aureliaGrid1.slickGrid?.invalidate(); + } + toggleSubTitle() { this.hideSubTitle = !this.hideSubTitle; const action = this.hideSubTitle ? 'add' : 'remove'; diff --git a/demos/aurelia/src/examples/slickgrid/example16.ts b/demos/aurelia/src/examples/slickgrid/example16.ts index fd8854884a..489f96b3fb 100644 --- a/demos/aurelia/src/examples/slickgrid/example16.ts +++ b/demos/aurelia/src/examples/slickgrid/example16.ts @@ -1,12 +1,4 @@ -import { - ExtensionName, - Filters, - Formatters, - type AureliaGridInstance, - type Column, - type GridOption, - type OnEventArgs, -} from 'aurelia-slickgrid'; +import { Filters, Formatters, type AureliaGridInstance, type Column, type GridOption, type OnEventArgs } from 'aurelia-slickgrid'; export class Example16 { aureliaGrid!: AureliaGridInstance; @@ -24,7 +16,7 @@ export class Example16 { } get rowMoveInstance() { - return this.aureliaGrid?.extensionService.getExtensionInstanceByName(ExtensionName.rowMoveManager); + return this.aureliaGrid?.extensionService.getExtensionInstanceByName('rowMoveManager'); } attached() { @@ -87,8 +79,8 @@ export class Example16 { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, }, - enableRowSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, diff --git a/demos/aurelia/src/examples/slickgrid/example18.ts b/demos/aurelia/src/examples/slickgrid/example18.ts index f740bad981..24f7665ba3 100644 --- a/demos/aurelia/src/examples/slickgrid/example18.ts +++ b/demos/aurelia/src/examples/slickgrid/example18.ts @@ -253,12 +253,13 @@ export class Example18 { initialGroupBy: ['duration'], }, darkMode: this._darkMode, - enableTextExport: true, - enableExcelExport: true, excelExportOptions: { sanitizeDataExport: true }, textExportOptions: { sanitizeDataExport: true }, externalResources: [this.excelExportService, this.pdfExportService, this.textExportService], - enablePdfExport: true, + // -- NOTE: registered resources are auto-enabled + // enableTextExport: true, + // enablePdfExport: true, + // enableExcelExport: true, pdfExportOptions: { repeatHeadersOnEachPage: true, // defaults to true documentTitle: 'Grouping Grid', diff --git a/demos/aurelia/src/examples/slickgrid/example19-detail-view.ts b/demos/aurelia/src/examples/slickgrid/example19-detail-view.ts index fec60a3283..6ca7ffd50c 100644 --- a/demos/aurelia/src/examples/slickgrid/example19-detail-view.ts +++ b/demos/aurelia/src/examples/slickgrid/example19-detail-view.ts @@ -1,4 +1,4 @@ -import type { SlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin'; +import type { AureliaSlickRowDetailView } from '@slickgrid-universal/aurelia-row-detail-plugin'; import { bindable } from 'aurelia'; import type { SlickDataView, SlickGrid } from 'aurelia-slickgrid'; import './example19-detail-view.scss'; @@ -19,7 +19,7 @@ export class Example19DetailView { @bindable() model!: Item; // you also have access to the following objects (it must match the exact property names shown below) - @bindable() addon!: SlickRowDetailView; // row detail addon instance + @bindable() addon!: AureliaSlickRowDetailView; // row detail addon instance @bindable() grid!: SlickGrid; @bindable() dataView!: SlickDataView; diff --git a/demos/aurelia/src/examples/slickgrid/example19.ts b/demos/aurelia/src/examples/slickgrid/example19.ts index 592d9df426..83d513ea5e 100644 --- a/demos/aurelia/src/examples/slickgrid/example19.ts +++ b/demos/aurelia/src/examples/slickgrid/example19.ts @@ -1,5 +1,6 @@ +import { AureliaSlickRowDetailView } from '@slickgrid-universal/aurelia-row-detail-plugin'; import { bindable } from 'aurelia'; -import { Editors, ExtensionName, Filters, Formatters, type AureliaGridInstance, type Column, type GridOption } from 'aurelia-slickgrid'; +import { Editors, Filters, Formatters, type AureliaGridInstance, type Column, type GridOption } from 'aurelia-slickgrid'; import { ExampleDetailPreload } from './example-detail-preload.js'; import { Example19DetailView } from './example19-detail-view.js'; @@ -32,7 +33,7 @@ export class Example19 { // return this.extensions.rowDetailView.instance || {}; // OR option 2 - return this.aureliaGrid?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView); + return this.aureliaGrid?.extensionService.getExtensionInstanceByName('rowDetailView'); } attached() { @@ -134,6 +135,7 @@ export class Example19 { rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" darkMode: this._darkMode, datasetIdPropertyName: 'rowId', // optionally use a different "id" + externalResources: [AureliaSlickRowDetailView], rowDetailView: { // optionally change the column index position of the icon (defaults to 0) // columnIndexPosition: 1, @@ -171,7 +173,7 @@ export class Example19 { // Optionally pass your Parent Component reference to your Child Component (row detail component) parentRef: this, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: true, }, diff --git a/demos/aurelia/src/examples/slickgrid/example2.ts b/demos/aurelia/src/examples/slickgrid/example2.ts index 913fc8a181..6030b3f685 100644 --- a/demos/aurelia/src/examples/slickgrid/example2.ts +++ b/demos/aurelia/src/examples/slickgrid/example2.ts @@ -129,7 +129,7 @@ export class Example2 { minWidth: 100, formatter: customEnableButtonFormatter, onCellClick: (_e, args) => { - this.toggleCompletedProperty(args && args.dataContext); + this.toggleCompletedProperty(args?.dataContext); }, }, ]; diff --git a/demos/aurelia/src/examples/slickgrid/example21.ts b/demos/aurelia/src/examples/slickgrid/example21.ts index 991e5370ed..0e266bb342 100644 --- a/demos/aurelia/src/examples/slickgrid/example21.ts +++ b/demos/aurelia/src/examples/slickgrid/example21.ts @@ -1,5 +1,5 @@ import { bindable } from 'aurelia'; -import { Formatters, type AureliaGridInstance, type Column, type GridOption, type OperatorString } from 'aurelia-slickgrid'; +import { Formatters, type AureliaGridInstance, type Column, type GridOption, type OperatorType } from 'aurelia-slickgrid'; import './example21.scss'; export class Example21 { @@ -11,7 +11,7 @@ export class Example21 { gridOptions!: GridOption; dataset: any[] = []; hideSubTitle = false; - operatorList: OperatorString[] = ['=', '<', '<=', '>', '>=', '<>', 'StartsWith', 'EndsWith']; + operatorList: OperatorType[] = ['=', '<', '<=', '>', '>=', '<>', 'StartsWith', 'EndsWith']; constructor() { // define the grid options & columns and then create the grid itself @@ -99,7 +99,7 @@ export class Example21 { alwaysShowVerticalScroll: false, enableColumnPicker: true, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, }; } @@ -150,7 +150,7 @@ export class Example21 { updateFilter() { this.aureliaGrid?.filterService.updateSingleFilter({ columnId: `${this.selectedColumn.id || ''}`, - operator: this.selectedOperator as OperatorString, + operator: this.selectedOperator as OperatorType, searchTerms: [this.searchValue || ''], }); } diff --git a/demos/aurelia/src/examples/slickgrid/example23.ts b/demos/aurelia/src/examples/slickgrid/example23.ts index 7bc4498c6c..989b13e618 100644 --- a/demos/aurelia/src/examples/slickgrid/example23.ts +++ b/demos/aurelia/src/examples/slickgrid/example23.ts @@ -7,7 +7,6 @@ import { resolve } from 'aurelia'; import { Filters, Formatters, - OperatorType, type AureliaGridInstance, type Column, type CurrentFilter, @@ -109,7 +108,7 @@ export class Example23 { filter: { model: Filters.sliderRange, maxValue: 100, // or you can use the options as well - operator: OperatorType.rangeInclusive, // defaults to inclusive + operator: 'RangeInclusive', // defaults to inclusive options: { hideSliderNumbers: false, // you can hide/show the slider numbers on both side min: 0, @@ -157,7 +156,7 @@ export class Example23 { filterable: true, filter: { model: Filters.input, - operator: OperatorType.rangeExclusive, // defaults to exclusive + operator: 'RangeExclusive', // defaults to exclusive }, }, { @@ -260,11 +259,11 @@ export class Example23 { } refreshMetrics(_e: Event, args: any) { - if (args && args.current >= 0) { + if (args?.current >= 0) { setTimeout(() => { this.metrics = { startTime: new Date(), - itemCount: (args && args.current) || 0, + itemCount: args?.current || 0, totalItemCount: this.dataset.length || 0, }; }); @@ -304,8 +303,8 @@ export class Example23 { switch (newPredefinedFilter) { case 'currentYearTasks': filters = [ - { columnId: 'finish', operator: OperatorType.rangeInclusive, searchTerms: [`${currentYear}-01-01`, `${currentYear}-12-31`] }, - { columnId: 'completed', operator: OperatorType.equal, searchTerms: [true] }, + { columnId: 'finish', operator: 'RangeInclusive', searchTerms: [`${currentYear}-01-01`, `${currentYear}-12-31`] }, + { columnId: 'completed', operator: '=', searchTerms: [true] }, ]; break; case 'nextYearTasks': diff --git a/demos/aurelia/src/examples/slickgrid/example24.ts b/demos/aurelia/src/examples/slickgrid/example24.ts index cf673d10f5..a24fd42b0d 100644 --- a/demos/aurelia/src/examples/slickgrid/example24.ts +++ b/demos/aurelia/src/examples/slickgrid/example24.ts @@ -3,7 +3,6 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; // import { TOptions as I18NOptions } from 'i18next'; import { resolve } from 'aurelia'; import { - ExtensionName, Filters, Formatters, type AureliaGridInstance, @@ -78,11 +77,11 @@ export class Example24 { } get cellMenuInstance() { - return this.aureliaGrid?.extensionService.getExtensionInstanceByName(ExtensionName.cellMenu); + return this.aureliaGrid?.extensionService.getExtensionInstanceByName('cellMenu'); } get contextMenuInstance() { - return this.aureliaGrid?.extensionService.getExtensionInstanceByName(ExtensionName.contextMenu); + return this.aureliaGrid?.extensionService.getExtensionInstanceByName('contextMenu'); } attached() { @@ -429,7 +428,7 @@ export class Example24 { // optionally and conditionally define when the the menu is usable, // this should be used with a custom formatter to show/hide/disable the menu menuUsabilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return dataContext.id < 21; // say we want to display the menu only from Task 0 to 20 }, // which column to show the command list? when not defined it will be shown over all columns @@ -460,7 +459,7 @@ export class Example24 { }, // only show command to 'Help' when the task is Not Completed itemVisibilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return !dataContext.completed; }, }, @@ -526,7 +525,7 @@ export class Example24 { textCssClass: 'italic', // only enable this option when the task is Not Completed itemUsabilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return !dataContext.completed; }, // you can use the 'action' callback and/or subscribe to the 'onCallback' event, they both have the same arguments @@ -548,7 +547,7 @@ export class Example24 { disabled: true, // only shown when the task is Not Completed itemVisibilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return !dataContext.completed; }, }, @@ -579,7 +578,7 @@ export class Example24 { // subscribe to Context Menu onOptionSelected event (or use the action callback on each option) onOptionSelected: (_e: any, args: any) => { // change Priority - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; if (dataContext?.hasOwnProperty('priority')) { dataContext.priority = args.item.option; this.aureliaGrid.gridService.updateItem(dataContext); diff --git a/demos/aurelia/src/examples/slickgrid/example25.ts b/demos/aurelia/src/examples/slickgrid/example25.ts index 7257d4adf6..6b3fb6d577 100644 --- a/demos/aurelia/src/examples/slickgrid/example25.ts +++ b/demos/aurelia/src/examples/slickgrid/example25.ts @@ -4,7 +4,6 @@ import { GraphqlService, type GraphqlResult, type GraphqlServiceApi } from '@sli import { Filters, Formatters, - OperatorType, type AureliaGridInstance, type Column, type GridOption, @@ -85,7 +84,7 @@ export class Example25 { filter: { model: Filters.multipleSelect, collectionAsync: this.getLanguages(), - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', collectionOptions: { addBlankEntry: true, // the data is not at the root of the array, so we must tell the Select Filter where to pull the data @@ -118,7 +117,7 @@ export class Example25 { filter: { model: Filters.multipleSelect, collectionAsync: this.getLanguages(), - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', collectionOptions: { addBlankEntry: true, // the data is not at the root of the array, so we must tell the Select Filter where to pull the data @@ -277,7 +276,7 @@ export class Example25 { setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService - this.aureliaGrid.filterService.updateFilters([{ columnId: 'countryName', searchTerms: ['G'], operator: OperatorType.startsWith }]); + this.aureliaGrid.filterService.updateFilters([{ columnId: 'countryName', searchTerms: ['G'], operator: 'StartsWith' }]); } setSortingDynamically() { diff --git a/demos/aurelia/src/examples/slickgrid/example26.ts b/demos/aurelia/src/examples/slickgrid/example26.ts index 5da11df4b8..499b058450 100644 --- a/demos/aurelia/src/examples/slickgrid/example26.ts +++ b/demos/aurelia/src/examples/slickgrid/example26.ts @@ -4,7 +4,6 @@ import { Editors, Filters, Formatters, - OperatorType, SlickGlobalEditorLock, type AureliaGridInstance, type Column, @@ -198,7 +197,7 @@ export class Example26 { collectionFilterBy: { property: 'value', value: 0, - operator: OperatorType.notEqual, + operator: '!=', }, model: Editors.singleSelect, }, diff --git a/demos/aurelia/src/examples/slickgrid/example3.ts b/demos/aurelia/src/examples/slickgrid/example3.ts index 433368c564..cb556556d1 100644 --- a/demos/aurelia/src/examples/slickgrid/example3.ts +++ b/demos/aurelia/src/examples/slickgrid/example3.ts @@ -5,7 +5,6 @@ import { Editors, Filters, Formatters, - OperatorType, SlickGlobalEditorLock, SortComparers, type AureliaGridInstance, @@ -29,7 +28,7 @@ const NB_ITEMS = 100; // you can create custom validator to pass to an inline editor const myCustomTitleValidator: EditorValidator = (value: any) => { // you can get the Editor Args which can be helpful, e.g. we can get the Translate Service from it - // const grid = args && args.grid; + // const grid = args?.grid; // const gridOptions = grid.getOptions() as GridOption; // const i18n = gridOptions.i18n; @@ -210,7 +209,7 @@ export class Example3 { collectionFilterBy: { property: 'value', value: 0, - operator: OperatorType.notEqual, + operator: 'NE', }, model: Editors.singleSelect, // validator: (value, args) => { @@ -433,7 +432,7 @@ export class Example3 { separatorBetweenTextLabels: ' ', }, model: Filters.multipleSelect, - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', }, }, ]; diff --git a/demos/aurelia/src/examples/slickgrid/example30.ts b/demos/aurelia/src/examples/slickgrid/example30.ts index eedf04f3af..dfe20edcdc 100644 --- a/demos/aurelia/src/examples/slickgrid/example30.ts +++ b/demos/aurelia/src/examples/slickgrid/example30.ts @@ -487,7 +487,7 @@ export class Example30 { }, externalResources: [new ExcelExportService(), new SlickCustomTooltip(), this.compositeEditorInstance], enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -495,7 +495,7 @@ export class Example30 { showPreHeaderPanel: true, preHeaderPanelHeight: 28, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, multiSelect: false, checkboxSelector: { hideInFilterHeaderRow: false, diff --git a/demos/aurelia/src/examples/slickgrid/example31.ts b/demos/aurelia/src/examples/slickgrid/example31.ts index 3da46675cb..9eb561115d 100644 --- a/demos/aurelia/src/examples/slickgrid/example31.ts +++ b/demos/aurelia/src/examples/slickgrid/example31.ts @@ -5,7 +5,6 @@ import { RxJsResource } from '@slickgrid-universal/rxjs-observable'; import { Editors, Filters, - OperatorType, type AureliaGridInstance, type Column, type GridOption, @@ -103,7 +102,7 @@ export class Example31 { enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enablePagination: true, // you could optionally disable the Pagination pagination: { pageSizes: [10, 20, 50, 100, 500], @@ -112,8 +111,8 @@ export class Example31 { presets: { // you can also type operator as string, e.g.: operator: 'EQ' filters: [ - // { columnId: 'name', searchTerms: ['w'], operator: OperatorType.startsWith }, - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'name', searchTerms: ['w'], operator: 'StartsWith' }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, ], sorters: [ // direction can be written as 'asc' (uppercase or lowercase) and/or use the SortDirection type @@ -384,7 +383,7 @@ export class Example31 { setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.aureliaGrid?.filterService.updateFilters([ - // { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'gender', searchTerms: ['male'], operator: '=' }, { columnId: 'name', searchTerms: ['A'], operator: 'a*' }, ]); } diff --git a/demos/aurelia/src/examples/slickgrid/example32.ts b/demos/aurelia/src/examples/slickgrid/example32.ts index fcb61dba44..7a4d4beac2 100644 --- a/demos/aurelia/src/examples/slickgrid/example32.ts +++ b/demos/aurelia/src/examples/slickgrid/example32.ts @@ -494,13 +494,13 @@ export class Example32 { }, externalResources: [new ExcelExportService()], enableFiltering: true, - enableRowSelection: true, + enableSelection: true, enableCheckboxSelector: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -585,7 +585,7 @@ export class Example32 { // just for demo purposes, set it back to its original width const columns = this.aureliaGrid.slickGrid.getColumns() as Column[]; columns.forEach((col) => (col.width = col.originalWidth)); - this.aureliaGrid.slickGrid.setColumns(columns); + this.aureliaGrid.slickGrid.updateColumns(); this.aureliaGrid.slickGrid.autosizeColumns(); this.isUsingDefaultResize = true; } diff --git a/demos/aurelia/src/examples/slickgrid/example33.html b/demos/aurelia/src/examples/slickgrid/example33.html index 80ec67fbca..f184dd4453 100644 --- a/demos/aurelia/src/examples/slickgrid/example33.html +++ b/demos/aurelia/src/examples/slickgrid/example33.html @@ -33,6 +33,24 @@

    + +
    Lazy loading collection... diff --git a/demos/aurelia/src/examples/slickgrid/example33.ts b/demos/aurelia/src/examples/slickgrid/example33.ts index d50506dadf..0371f6f92c 100644 --- a/demos/aurelia/src/examples/slickgrid/example33.ts +++ b/demos/aurelia/src/examples/slickgrid/example33.ts @@ -1,10 +1,10 @@ import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { + createDomElement, Editors, Filters, Formatters, - OperatorType, type AureliaGridInstance, type Column, type EditCommand, @@ -141,6 +141,31 @@ export class Example33 { // maxHeight: 30, }, }, + { + id: 'button', + name: 'Button Tooltip', + field: 'title', + width: 100, + minWidth: 100, + filterable: true, + excludeFromExport: true, + formatter: (_row, _cell, value) => { + const button = createDomElement('button', { + className: 'btn btn-outline-secondary btn-icon btn-sm', + title: 'This is the button tooltip', + }); + const icon = createDomElement('i', { className: 'mdi mdi-information', title: 'icon tooltip' }); + const text = createDomElement('span', { textContent: 'Hello Task' }); + button.appendChild(icon); + button.appendChild(text); + button.addEventListener('click', () => alert(`Clicked button for ${value}`)); + return button; + }, + // define tooltip options here OR for the entire grid via the grid options (cell tooltip options will have precedence over grid options) + customTooltip: { + useRegularTooltip: true, // note regular tooltip will try to find a "title" attribute in the cell formatter (it won't work without a cell formatter) + }, + }, { id: 'cost', name: 'Cost', @@ -320,7 +345,7 @@ export class Example33 { collectionOptions: { separatorBetweenTextLabels: ' ' }, options: { minHeight: 70 } as MultipleSelectOption, model: Filters.multipleSelect, - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', }, }, { @@ -403,19 +428,21 @@ export class Example33 { headerFormatter: this.headerFormatter, headerRowFormatter: this.headerRowFormatter, usabilityOverride: (args) => args.cell !== 0 && args?.column?.id !== 'action', // don't show on first/last columns + observeAllTooltips: true, // observe all elements with title/data-slick-tooltip attributes (not just SlickGrid elements) + observeTooltipContainer: 'body', // defaults to 'body', target a specific container (only works when observeAllTooltips is enabled) }, presets: { filters: [{ columnId: 'prerequisites', searchTerms: [1, 3, 5, 7, 9, 12, 15, 18, 21, 25, 28, 29, 30, 32, 34] }], }, - rowHeight: 33, + rowHeight: 38, enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, showCustomFooter: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, @@ -432,7 +459,7 @@ export class Example33 { onCommand: (e, args) => this.executeCommand(e, args), onOptionSelected: (_e, args) => { // change "Completed" property with new option selected from the Cell Menu - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; if (dataContext && dataContext.hasOwnProperty('completed')) { dataContext.completed = args.item.option; this.aureliaGrid.gridService.updateItem(dataContext); @@ -457,7 +484,7 @@ export class Example33 { id: i, title: 'Task ' + i, duration: Math.round(Math.random() * 100), - description: `This is a sample task description.\nIt can be multiline\r\rAnother line...`, + description: i > 500 ? null : `This is a sample task description.\nIt can be multiline\r\rAnother line...`, percentComplete: Math.floor(Math.random() * (100 - 5 + 1) + 5), start: new Date(randomYear, randomMonth, randomDay), finish: randomFinish < new Date() ? '' : randomFinish, // make sure the random date is earlier than today @@ -565,4 +592,18 @@ export class Example33 { document.querySelector('.subtitle')?.classList[action]('hidden'); this.aureliaGrid.resizerService.resizeGrid(0); } + + setFiltersDynamically(operator: string) { + const operatorType = operator === '=' ? '=' : '!='; + this.aureliaGrid.filterService.updateFilters( + [ + { + columnId: 'desc', + operator: operatorType, + searchTerms: [''], + }, + ], + true + ); + } } diff --git a/demos/aurelia/src/examples/slickgrid/example38.ts b/demos/aurelia/src/examples/slickgrid/example38.ts index 707be0a852..c7b0c96843 100644 --- a/demos/aurelia/src/examples/slickgrid/example38.ts +++ b/demos/aurelia/src/examples/slickgrid/example38.ts @@ -93,7 +93,7 @@ export class Example38 { enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enableGrouping: true, headerMenu: { hideFreezeColumnsCommand: false, diff --git a/demos/aurelia/src/examples/slickgrid/example4.ts b/demos/aurelia/src/examples/slickgrid/example4.ts index 32d776f7c3..947053c703 100644 --- a/demos/aurelia/src/examples/slickgrid/example4.ts +++ b/demos/aurelia/src/examples/slickgrid/example4.ts @@ -4,7 +4,6 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { Filters, Formatters, - OperatorType, type AureliaGridInstance, type Column, type GridOption, @@ -97,12 +96,12 @@ export class Example4 { collectionFilterBy: [ { property: 'value', - operator: OperatorType.notEqual, + operator: '!=', value: 360, }, { property: 'value', - operator: OperatorType.notEqual, + operator: '!=', value: 365, }, ], @@ -313,12 +312,12 @@ export class Example4 { } refreshMetrics(_e: Event, args: any) { - if (args && args.current >= 0) { + if (args?.current >= 0) { setTimeout(() => { this.metrics = { startTime: new Date(), endTime: new Date(), - itemCount: (args && args.current) || 0, + itemCount: args?.current || 0, totalItemCount: this.dataset.length || 0, }; }); diff --git a/demos/aurelia/src/examples/slickgrid/example41.ts b/demos/aurelia/src/examples/slickgrid/example41.ts index 13e3cf9df0..8e0b733887 100644 --- a/demos/aurelia/src/examples/slickgrid/example41.ts +++ b/demos/aurelia/src/examples/slickgrid/example41.ts @@ -49,11 +49,12 @@ export class Example41 { gridWidth: 800, rowHeight: 33, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, enableRowMoveManager: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, + selectionType: 'row', }, rowMoveManager: { columnIndexPosition: 0, diff --git a/demos/aurelia/src/examples/slickgrid/example42.ts b/demos/aurelia/src/examples/slickgrid/example42.ts index 1f57abdbe4..76a608ec62 100644 --- a/demos/aurelia/src/examples/slickgrid/example42.ts +++ b/demos/aurelia/src/examples/slickgrid/example42.ts @@ -2,7 +2,6 @@ import { bindable } from 'aurelia'; import { Filters, Formatters, - OperatorType, type AureliaGridInstance, type Column, type GridOption, @@ -74,7 +73,7 @@ export class Example42 { filter: { model: Filters.sliderRange, maxValue: 100, // or you can use the options as well - operator: OperatorType.rangeInclusive, // defaults to inclusive + operator: 'RangeInclusive', // defaults to inclusive options: { hideSliderNumbers: false, // you can hide/show the slider numbers on both side min: 0, @@ -120,7 +119,7 @@ export class Example42 { filterable: true, filter: { model: Filters.input, - operator: OperatorType.rangeExclusive, // defaults to exclusive + operator: 'RangeExclusive', // defaults to exclusive }, }, { diff --git a/demos/aurelia/src/examples/slickgrid/example43.ts b/demos/aurelia/src/examples/slickgrid/example43.ts index 696cd6e89a..5283c13bd9 100644 --- a/demos/aurelia/src/examples/slickgrid/example43.ts +++ b/demos/aurelia/src/examples/slickgrid/example43.ts @@ -427,7 +427,8 @@ export class Example43 { } } - // update column definitions + // 1. update column definitions via grid.setColumns() + // this will shift colspan/rowspan to the left or right accordingly if (this.showEmployeeId) { this.columnDefinitions.unshift({ id: 'employeeID', name: 'Employee ID', field: 'employeeID', width: 100 }); } else { @@ -435,6 +436,22 @@ export class Example43 { } this.aureliaGrid.slickGrid.setColumns(this.columnDefinitions); + // --- OR --- + // 2. OR update via "hidden" column flag & increase/decrease column index accordingly in the metadata + // this approach will keep colspan/rowspan "as-is" but will hide the EmployeeID column + /* + const colDirIdx = newShowEmployeeId ? -1 : 1; + for (const row of Object.keys(this.metadata)) { + newMetadata[row] = { columns: {} }; + for (const col of Object.keys((this.metadata as any)[row].columns)) { + newMetadata[row].columns[Number(col) + colDirIdx] = (this.metadata as any)[row].columns[col]; + } + } + this.aureliaGrid.slickGrid?.setOptions({ frozenColumn: newShowEmployeeId ? 0 : 1 }); + this.aureliaGrid.slickGrid?.updateColumnById('employeeID', { hidden: !newShowEmployeeId }); + this.aureliaGrid.slickGrid?.updateColumns(); + */ + // update & remap rowspans this.metadata = newMetadata; this.aureliaGrid.slickGrid.remapAllColumnsRowSpan(); diff --git a/demos/aurelia/src/examples/slickgrid/example45.ts b/demos/aurelia/src/examples/slickgrid/example45.ts index 038861db07..94e7e2e107 100644 --- a/demos/aurelia/src/examples/slickgrid/example45.ts +++ b/demos/aurelia/src/examples/slickgrid/example45.ts @@ -1,6 +1,7 @@ import { faker } from '@faker-js/faker'; +import { AureliaSlickRowDetailView } from '@slickgrid-universal/aurelia-row-detail-plugin'; import { bindable } from 'aurelia'; -import { ExtensionName, type AureliaGridInstance, type Column, type GridOption, type SlickRowDetailView } from 'aurelia-slickgrid'; +import { type AureliaGridInstance, type Column, type GridOption } from 'aurelia-slickgrid'; import { Example45DetailView, type Distributor, type OrderData } from './example45-detail-view.js'; import { Example45Preload } from './example45-preload.js'; @@ -21,7 +22,7 @@ export class Example45 { hideSubTitle = false; get rowDetailInstance() { - return this.aureliaGrid?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView) as SlickRowDetailView; + return this.aureliaGrid?.extensionService.getExtensionInstanceByName('rowDetailView') as AureliaSlickRowDetailView; } aureliaGridReady(aureliaGrid: AureliaGridInstance) { @@ -101,6 +102,7 @@ export class Example45 { rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" darkMode: this._darkMode, rowHeight: 33, + externalResources: [AureliaSlickRowDetailView], rowDetailView: { process: (item) => this.simulateServerAsyncCall(item), loadOnce: false, // you can't use loadOnce with inner grid because only HTML template are re-rendered, not JS events diff --git a/demos/aurelia/src/examples/slickgrid/example46.ts b/demos/aurelia/src/examples/slickgrid/example46.ts index 378e4db940..28380b5829 100644 --- a/demos/aurelia/src/examples/slickgrid/example46.ts +++ b/demos/aurelia/src/examples/slickgrid/example46.ts @@ -120,7 +120,7 @@ export class Example46 { sanitizeDataExport: true, }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, multiSelect: false, checkboxSelector: { // columnIndexPosition: 1, diff --git a/demos/aurelia/src/examples/slickgrid/example47-detail-view.ts b/demos/aurelia/src/examples/slickgrid/example47-detail-view.ts index d11a5b648b..a79c5cee02 100644 --- a/demos/aurelia/src/examples/slickgrid/example47-detail-view.ts +++ b/demos/aurelia/src/examples/slickgrid/example47-detail-view.ts @@ -1,4 +1,4 @@ -import type { SlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin'; +import type { AureliaSlickRowDetailView } from '@slickgrid-universal/aurelia-row-detail-plugin'; import { bindable } from 'aurelia'; import type { SlickDataView, SlickGrid } from 'aurelia-slickgrid'; import { showToast } from './utilities.js'; @@ -20,7 +20,7 @@ export class Example47DetailView { @bindable() model!: Item; // you also have access to the following objects (it must match the exact property names shown below) - @bindable() addon!: SlickRowDetailView; // row detail addon instance + @bindable() addon!: AureliaSlickRowDetailView; // row detail addon instance @bindable() grid!: SlickGrid; @bindable() dataView!: SlickDataView; diff --git a/demos/aurelia/src/examples/slickgrid/example47.ts b/demos/aurelia/src/examples/slickgrid/example47.ts index 007581037e..77b5cbc57e 100644 --- a/demos/aurelia/src/examples/slickgrid/example47.ts +++ b/demos/aurelia/src/examples/slickgrid/example47.ts @@ -1,3 +1,4 @@ +import { AureliaSlickRowDetailView } from '@slickgrid-universal/aurelia-row-detail-plugin'; import { bindable } from 'aurelia'; import { Aggregators, @@ -57,6 +58,7 @@ export class Example47 { // option 1 // return this.extensions.rowDetailView.instance || {}; + // return this.aureliaGrid?.extensions.rowDetailView.instance || {}; // OR option 2 return this.aureliaGrid?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView); @@ -181,6 +183,7 @@ export class Example47 { enableRowDetailView: true, rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" darkMode: this._darkMode, + externalResources: [AureliaSlickRowDetailView], rowDetailView: { // optionally change the column index position of the icon (defaults to 0) // columnIndexPosition: 1, @@ -209,7 +212,7 @@ export class Example47 { // Optionally pass your Parent Component reference to your Child Component (row detail component) parentRef: this, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: true, }, diff --git a/demos/aurelia/src/examples/slickgrid/example48.html b/demos/aurelia/src/examples/slickgrid/example48.html index e484fb2e9a..143d058e6f 100644 --- a/demos/aurelia/src/examples/slickgrid/example48.html +++ b/demos/aurelia/src/examples/slickgrid/example48.html @@ -16,7 +16,7 @@

    SlickHybridSelectionModel This Selection Model is an hybrid approach that uses a combination of the row or cell selections - depending on certain conditions. Use enableHybridSelection grid option to enable the new Hybrid Selection Model. + depending on certain conditions.
    • 1. clicking on the first column (id) will use RowSelectionModel because of our configuration of @@ -54,6 +54,7 @@

      +
      Spreadsheet with drag-fill, hybrid selection model. Type a few values in the grid and then select those cells and use the bottom right drag handle spread the selection and auto-fill the values to other cells. Use onDragReplaceCells event to customize the - drag-fill behavior. Use enableHybridSelection grid option to enable the new Hybrid Selection Model. + drag-fill behavior. Use { enableSelection: true, selectionOptions: { selectionType: 'mixed' }} + grid option to enable the new Hybrid Selection Model.
      diff --git a/demos/aurelia/src/examples/slickgrid/example49.ts b/demos/aurelia/src/examples/slickgrid/example49.ts index 5c292a7acf..c720da9654 100644 --- a/demos/aurelia/src/examples/slickgrid/example49.ts +++ b/demos/aurelia/src/examples/slickgrid/example49.ts @@ -76,10 +76,11 @@ export class Example49 { editorNavigateOnArrows: true, // enable editor navigation using arrow keys // enable new hybrid selection model (rows & cells) - enableHybridSelection: true, - rowSelectionOptions: { - selectActiveRow: true, + enableSelection: true, + selectionOptions: { rowSelectColumnIds: ['selector'], + selectActiveRow: true, + selectionType: 'mixed', }, // when using the ExcelCopyBuffer, you can see what the selection range is diff --git a/demos/aurelia/src/examples/slickgrid/example5.ts b/demos/aurelia/src/examples/slickgrid/example5.ts index 9357d4595e..cb02baa067 100644 --- a/demos/aurelia/src/examples/slickgrid/example5.ts +++ b/demos/aurelia/src/examples/slickgrid/example5.ts @@ -3,7 +3,6 @@ import { newInstanceOf, resolve } from '@aurelia/kernel'; import { GridOdataService, type OdataOption, type OdataServiceApi } from '@slickgrid-universal/odata'; import { Filters, - OperatorType, type AureliaGridInstance, type Column, type GridOption, @@ -96,13 +95,13 @@ export class Example5 { hideInColumnTitleRow: true, }, compoundOperatorAltTexts: { - // where '=' is any of the `OperatorString` type shown above + // where '=' is any of the `OperatorType` type shown above text: { Custom: { operatorAlt: '%%', descAlt: 'SQL Like' } }, }, enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enablePagination: true, // you could optionally disable the Pagination pagination: { pageSizes: [10, 20, 50, 100, 500, 50000], @@ -111,7 +110,7 @@ export class Example5 { }, presets: { // you can also type operator as string, e.g.: operator: 'EQ' - filters: [{ columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }], + filters: [{ columnId: 'gender', searchTerms: ['male'], operator: '=' }], sorters: [ // direction can be written as 'asc' (uppercase or lowercase) and/or use the SortDirection type { columnId: 'name', direction: 'asc' }, @@ -125,7 +124,7 @@ export class Example5 { enableSelect: this.isSelectEnabled, enableExpand: this.isExpandEnabled, filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { let matchesSearch = searchValues[0].replace(/\*/g, '.*'); matchesSearch = matchesSearch.slice(0, 1) + CARET_HTML_ESCAPED + matchesSearch.slice(1); matchesSearch = matchesSearch.slice(0, -1) + "$'"; @@ -411,7 +410,7 @@ export class Example5 { setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.aureliaGrid.filterService.updateFilters([ - // { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'gender', searchTerms: ['male'], operator: '=' }, { columnId: 'name', searchTerms: ['A'], operator: 'a*' }, ]); } diff --git a/demos/aurelia/src/examples/slickgrid/example50.ts b/demos/aurelia/src/examples/slickgrid/example50.ts index 90a5cfc7c4..c16ae4fcc9 100644 --- a/demos/aurelia/src/examples/slickgrid/example50.ts +++ b/demos/aurelia/src/examples/slickgrid/example50.ts @@ -58,8 +58,8 @@ export class Example50 { gridHeight: 225, gridWidth: 800, rowHeight: 33, - enableHybridSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { selectionType: 'row', }, }; diff --git a/demos/aurelia/src/examples/slickgrid/example51.html b/demos/aurelia/src/examples/slickgrid/example51.html new file mode 100644 index 0000000000..5255b2d467 --- /dev/null +++ b/demos/aurelia/src/examples/slickgrid/example51.html @@ -0,0 +1,75 @@ +
      +

      + Example 51: Menus with Slots + + + code + + + + +

      + +
      +
      + + Menu Slots Demo with Custom Renderer +
      +

      + Click on the menu buttons to see the new single slot functionality working across all menu types (Header Menu, Cell + Menu, Context Menu, Grid Menu): +

      +

      + Note: The demo focuses on the custom rendering capability via slotRenderer and + defaultMenuItemRenderer, which work across all menu plugins (SlickHeaderMenu, SlickCellMenu, SlickContextMenu, + SlickGridMenu). Also note that the keyboard shortcuts displayed in the menus (e.g., Alt+โ†‘, F5) are for + demo purposes only and do not actually trigger any actions. + +

      +
      + +
      +
      + + + + +
      +
      + +
      + + +
      +
      diff --git a/demos/aurelia/src/examples/slickgrid/example51.scss b/demos/aurelia/src/examples/slickgrid/example51.scss new file mode 100644 index 0000000000..539ad2ecd2 --- /dev/null +++ b/demos/aurelia/src/examples/slickgrid/example51.scss @@ -0,0 +1,144 @@ +body { + --slick-menu-item-height: 30px; + --slick-menu-line-height: 30px; + --slick-column-picker-item-height: 28px; + --slick-column-picker-line-height: 28px; + --slick-menu-item-border-radius: 4px; + --slick-menu-item-hover-border: 1px solid #148dff; + --slick-column-picker-item-hover-color: #fff; + --slick-column-picker-item-border-radius: 4px; + --slick-column-picker-item-hover-border: 1px solid #148dff; + --slick-menu-item-hover-color: #fff; + --slick-tooltip-background-color: #4c4c4c; + --slick-tooltip-color: #fff; + --slick-tooltip-font-size: 14px; + .slick-cell-menu, + .slick-context-menu, + .slick-grid-menu, + .slick-header-menu { + .slick-menu-item:hover:not(.slick-menu-item-disabled) { + color: #0a34b5; + } + } + .slick-menu-footer { + padding: 4px 6px; + border-top: 1px solid #c0c0c0; + } +} + +kbd { + background-color: #eee; + color: #202020; +} +.key-hint { + background: #eee; + border: 1px solid #ccc; + border-radius: 2px; + padding: 2px 4px; + font-size: 10px; + margin-left: 10px; + white-space: nowrap; + display: inline-flex; + align-items: center; + height: 20px; + + &.beta, + &.danger, + &.warn { + color: white; + font-size: 8px; + font-weight: bold; + } + &.beta { + background: #4444ff; + border: 1px solid #5454ff; + } + + &.danger { + background: #ff4444; + border: 1px solid #fb5a5a; + } + + &.warn { + background: #ff9800; + border: 1px solid #fba321; + } +} + +.edit-cell { + // background: #eee; + border: 1px solid #ccc; + border-radius: 2px; + padding: 2px 4px; + font-size: 10px; + margin-left: 10px; + display: inline-flex; + align-items: center; + height: 18px; +} + +.export-timestamp { + background-color: #4c4c4c; + color: #fff; + padding: 8px; + border-radius: 4px; + position: absolute; + z-index: 999999; +} + +.advanced-export-icon, +.edit-cell-icon, +.recalc-icon { + width: 20px; + height: 20px; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 4px; + transition: transform 0.2s; + color: white; + font-size: 10px; +} +.advanced-export-icon { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} +.edit-cell-icon { + background: linear-gradient(135deg, #00c853 0%, #64dd17 100%); +} +.recalc-icon { + background: linear-gradient(135deg, #c800a3 0%, #a31189 100%); +} + +.round-tag { + width: 6px; + height: 6px; + border-radius: 50%; + display: inline-block; + background: #44ff44; + box-shadow: 0 0 4px #44ff44; + margin-left: 10px; +} + +.menu-item { + display: flex; + align-items: center; + flex: 1; + justify-content: space-between; + + .menu-item-label.warn { + flex: 1; + color: #f09000; + } +} +.menu-item-icon { + margin-right: 4px; + font-size: 18px; + &.warn { + color: #ff9800; + } +} + +.menu-item-label { + flex: 1; +} diff --git a/demos/aurelia/src/examples/slickgrid/example51.ts b/demos/aurelia/src/examples/slickgrid/example51.ts new file mode 100644 index 0000000000..32e54a15de --- /dev/null +++ b/demos/aurelia/src/examples/slickgrid/example51.ts @@ -0,0 +1,611 @@ +import { format as tempoFormat } from '@formkit/tempo'; +import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; +import { + Aggregators, + createDomElement, + Filters, + Formatters, + SortComparers, + SortDirectionNumber, + type AureliaGridInstance, + type Column, + type GridOption, + type Grouping, + type MenuCommandItem, +} from 'aurelia-slickgrid'; +import './example51.scss'; + +const NB_ITEMS = 2000; + +interface ReportItem { + id: number; + title: string; + duration: number; + cost: number; + percentComplete: number; + start: Date; + finish: Date; + action?: string; +} + +export class Example51 { + aureliaGrid!: AureliaGridInstance; + columnDefinitions: Column[] = []; + gridOptions!: GridOption; + dataset!: ReportItem[]; + hideSubTitle = false; + + constructor() { + // define the grid options & columns and then create the grid itself + this.defineGrid(); + } + + attached() { + // mock some data (different in each dataset) + this.dataset = this.loadData(NB_ITEMS); + } + + aureliaGridReady(aureliaGrid: AureliaGridInstance) { + this.aureliaGrid = aureliaGrid; + } + + defineGrid() { + this.columnDefinitions = [ + { + id: 'title', + name: 'Title', + field: 'title', + sortable: true, + filterable: true, + minWidth: 100, + // Demo: Header Menu with Slot - complete custom HTML with keyboard shortcuts + header: { + menu: { + commandItems: [ + { + command: 'sort-asc', + title: 'Sort Ascending', + positionOrder: 50, + // Slot renderer replaces entire menu item content (can be HTML string or native DOM elements) + slotRenderer: (cmdItem) => ` + + `, + }, + { + command: 'sort-desc', + title: 'Sort Descending', + positionOrder: 51, + // Slot renderer using native DOM elements + slotRenderer: () => { + const menuItemElm = createDomElement('div', { className: 'menu-item' }); + const iconElm = createDomElement('i', { className: 'mdi mdi-sort-descending menu-item-icon' }); + const menuItemLabelElm = createDomElement('span', { className: 'menu-item-label', textContent: 'Sort Descending' }); + const kbdElm = createDomElement('kbd', { className: 'key-hint', textContent: 'Alt+โ†“' }); + menuItemElm.appendChild(iconElm); + menuItemElm.appendChild(menuItemLabelElm); + menuItemElm.appendChild(kbdElm); + return menuItemElm; + }, + }, + ], + }, + }, + }, + { + id: 'duration', + name: 'Duration', + field: 'duration', + sortable: true, + filterable: true, + minWidth: 100, + // Demo: Header Menu with Slot - showing badge and status dot + header: { + menu: { + commandItems: [ + { + command: 'column-resize-by-content', + title: 'Resize by Content', + positionOrder: 47, + // Slot renderer with badge + slotRenderer: () => ` + + `, + }, + { divider: true, command: '', positionOrder: 48 }, + { + command: 'sort-asc', + title: 'Sort Ascending', + iconCssClass: 'mdi mdi-sort-ascending', + positionOrder: 50, + }, + { + command: 'sort-desc', + title: 'Sort Descending', + iconCssClass: 'mdi mdi-sort-descending', + positionOrder: 51, + }, + { divider: true, command: '', positionOrder: 52 }, + { + command: 'clear-sort', + title: 'Remove Sort', + positionOrder: 58, + // Slot renderer with status indicator + slotRenderer: () => ` + + `, + }, + ], + }, + }, + }, + { + id: 'start', + name: 'Start', + field: 'start', + sortable: true, + formatter: Formatters.dateIso, + filterable: true, + filter: { model: Filters.compoundDate }, + minWidth: 100, + }, + { + id: 'finish', + name: 'Finish', + field: 'finish', + sortable: true, + formatter: Formatters.dateIso, + filterable: true, + filter: { model: Filters.dateRange }, + minWidth: 100, + }, + { + id: 'cost', + name: 'Cost', + field: 'cost', + width: 90, + sortable: true, + filterable: true, + formatter: Formatters.dollar, + // Demo: Header Menu with Slot - showing slotRenderer with callback (item, args) + header: { + menu: { + commandItems: [ + { + command: 'custom-action', + title: 'Advanced Export', + // Demo: Native HTMLElement with event listeners using slotRenderer (full DOM control) + slotRenderer: (cmdItem) => { + // you can use `createDomElement()` from Slickgrid for easier DOM element creation + const containerDiv = createDomElement('div', { className: 'menu-item' }); + const iconDiv = createDomElement('div', { className: 'advanced-export-icon', textContent: '๐Ÿ“Š' }); + const textSpan = createDomElement('span', { textContent: cmdItem.title || '', style: { flex: '1' } }); + const kbdElm = createDomElement('kbd', { className: 'key-hint', textContent: 'Ctrl+E' }); + containerDiv.appendChild(iconDiv); + containerDiv.appendChild(textSpan); + containerDiv.appendChild(kbdElm); + + // Add native event listeners for hover effects + containerDiv.addEventListener('mouseover', () => { + iconDiv.style.transform = 'scale(1.15)'; + iconDiv.style.background = 'linear-gradient(135deg, #d8dcef 0%, #ffffff 100%)'; + containerDiv.parentElement!.style.backgroundColor = '#854685'; + containerDiv.parentElement!.title = `๐Ÿ“ˆ Export timestamp: ${tempoFormat(new Date(), 'YYYY-MM-DD hh:mm:ss a')}`; + containerDiv.style.color = 'white'; + containerDiv.querySelector('.key-hint')!.style.color = 'black'; + }); + containerDiv.addEventListener('mouseout', () => { + iconDiv.style.transform = 'scale(1)'; + iconDiv.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; + containerDiv.parentElement!.style.backgroundColor = 'white'; + containerDiv.style.color = 'black'; + document.querySelector('.export-timestamp')?.remove(); + }); + + return containerDiv; + }, + action: () => { + alert('Custom export action triggered!'); + }, + }, + { divider: true, command: '' }, + { + command: 'filter-column', + title: 'Filter Column', + // Slot renderer with status indicator and beta badge + slotRenderer: () => ` + + `, + }, + ], + }, + }, + }, + { + id: 'percentComplete', + name: '% Complete', + field: 'percentComplete', + sortable: true, + filterable: true, + type: 'number', + filter: { model: Filters.slider, operator: '>=' }, + // Demo: Header Menu with Slot - showing interactive element (checkbox) + header: { + menu: { + commandItems: [ + { + command: 'recalc', + title: 'Recalculate', + iconCssClass: 'mdi mdi-refresh', + slotRenderer: () => ` + + `, + }, + ], + }, + }, + }, + { + id: 'action', + name: 'Action', + field: 'action', + width: 70, + minWidth: 70, + maxWidth: 70, + cssClass: 'justify-center flex', + formatter: () => + `
      `, + excludeFromExport: true, + // Demo: Cell Menu with slot examples (demonstrating defaultMenuItemRenderer at menu level) + cellMenu: { + hideCloseButton: false, + commandTitle: 'Cell Actions', + // Demo: Menu-level default renderer that applies to all items unless overridden + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + commandItems: [ + { + command: 'copy-cell', + title: 'Copy Cell Value', + iconCssClass: 'mdi mdi-content-copy', + action: (_e, args) => { + console.log('Copy cell value:', args.dataContext[args.column.field]); + alert(`Copied: ${args.dataContext[args.column.field]}`); + }, + }, + 'divider', + { + command: 'export-row', + title: 'Export Row', + iconCssClass: 'mdi mdi-download', + action: (_e, args) => { + console.log('Export row:', args.dataContext); + alert(`Export row #${args.dataContext.id}`); + }, + }, + { + command: 'export', + title: 'Export', + iconCssClass: 'mdi mdi-download', + commandItems: [ + { + command: 'export-excel', + title: 'Export as Excel', + iconCssClass: 'mdi mdi-file-excel-outline text-success', + action: (_e, args) => { + alert(`Export row #${args.dataContext.id} to Excel`); + }, + }, + { + command: 'export-csv', + title: 'Export as CSV', + iconCssClass: 'mdi mdi-file-document-outline', + action: (_e, args) => { + alert(`Export row #${args.dataContext.id} to CSV`); + }, + }, + { + command: 'export-pdf', + title: 'Export as PDF', + iconCssClass: 'mdi mdi-file-pdf-outline text-red', + action: (_e, args) => { + alert(`Export row #${args.dataContext.id} to PDF`); + }, + }, + ], + }, + { divider: true, command: '' }, + { + command: 'edit-row', + title: 'Edit Row', + // Individual slotRenderer overrides the defaultMenuItemRenderer + slotRenderer: (_item, args) => ` + + `, + action: (_e, args) => { + console.log('Edit row:', args.dataContext); + alert(`Edit row #${args.dataContext.id}`); + }, + }, + { + command: 'delete-row', + title: 'Delete Row', + iconCssClass: 'mdi mdi-delete text-danger', + action: (_event, args) => { + const dataContext = args.dataContext; + if (confirm(`Do you really want to delete row (${args.row! + 1}) with "${dataContext.title}"`)) { + this.aureliaGrid?.gridService.deleteItemById(dataContext.id); + } + }, + }, + ], + }, + }, + ]; + + this.gridOptions = { + autoResize: { + container: '#demo-container', + }, + enableAutoResize: true, + enableCellNavigation: true, + enableFiltering: true, + enableSorting: true, + enableGrouping: true, + + // Header Menu with slots (already configured in columns above) + enableHeaderMenu: true, + headerMenu: { + // hideCommands: ['column-resize-by-content', 'clear-sort'], + + // Demo: Menu-level default renderer for all header menu items + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + }, + + // Cell Menu with slots (configured in the Action column above) + enableCellMenu: true, + + // Context Menu with slot examples + enableContextMenu: true, + contextMenu: { + // hideCommands: ['clear-grouping', 'copy'], + + // build your command items list + // spread built-in commands and optionally filter/sort them however you want + commandListBuilder: (builtInItems) => { + // commandItems.sort((a, b) => (a === 'divider' || b === 'divider' ? 0 : a.title! > b.title! ? -1 : 1)); + return [ + // filter commands if you want + // ...builtInItems.filter((x) => x !== 'divider' && x.command !== 'copy' && x.command !== 'clear-grouping'), + { + command: 'edit-cell', + title: 'Edit Cell', + // Demo: Individual slotRenderer overrides the menu's defaultMenuItemRenderer + slotRenderer: (cmdItem) => { + // you can use `createDomElement()` from Slickgrid for easier DOM element creation + const containerDiv = createDomElement('div', { className: 'menu-item' }); + const iconDiv = createDomElement('div', { className: 'edit-cell-icon', textContent: 'โœŽ' }); + const textSpan = createDomElement('span', { textContent: cmdItem.title || '', style: { flex: '1' } }); + const kbdElm = createDomElement('kbd', { className: 'edit-cell', textContent: 'F2' }); + containerDiv.appendChild(iconDiv); + containerDiv.appendChild(textSpan); + containerDiv.appendChild(kbdElm); + + // Native event listeners for interactive effects + containerDiv.addEventListener('mouseover', () => { + iconDiv.style.transform = 'rotate(15deg) scale(1.1)'; + iconDiv.style.boxShadow = '0 2px 8px rgba(0,200,83,0.4)'; + }); + containerDiv.addEventListener('mouseout', () => { + iconDiv.style.transform = 'rotate(0deg) scale(1)'; + iconDiv.style.boxShadow = 'none'; + }); + + return containerDiv; + }, + action: () => alert('Edit cell'), + }, + ...builtInItems, + { divider: true, command: '' }, + { + command: 'export', + title: 'Export', + iconCssClass: 'mdi mdi-download', + commandItems: [ + { + command: 'export-excel', + title: 'Export as Excel', + iconCssClass: 'mdi mdi-file-excel-outline text-success', + action: () => alert('Export to Excel'), + }, + { + command: 'export-csv', + title: 'Export as CSV', + iconCssClass: 'mdi mdi-file-document-outline', + action: () => alert('Export to CSV'), + }, + { + command: 'export-pdf', + title: 'Export as PDF', + iconCssClass: 'mdi mdi-file-pdf-outline text-danger', + action: () => alert('Export to PDF'), + }, + ], + }, + { divider: true, command: '' }, + { + command: 'delete-row', + title: 'Delete Row', + iconCssClass: 'mdi mdi-delete text-danger', + action: () => alert('Delete row'), + }, + ] as Array; + }, + // Demo: Menu-level default renderer for context menu items + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + }, + + // Grid Menu with slot examples (demonstrating defaultMenuItemRenderer at menu level) + enableGridMenu: true, + gridMenu: { + // hideCommands: ['toggle-preheader', 'toggle-filter'], + + // Demo: Menu-level default renderer that applies to all items (can be overridden per item with slotRenderer) + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + commandListBuilder: (builtInItems) => { + return [ + ...builtInItems, + { divider: true, command: '' }, + { + command: 'export-excel', + title: 'Export to Excel', + iconCssClass: 'mdi mdi-file-excel-outline', + action: () => alert('Export to Excel'), + }, + { + command: 'export-csv', + title: 'Export to CSV', + iconCssClass: 'mdi mdi-download', + // Individual slotRenderer overrides the defaultMenuItemRenderer for this item + slotRenderer: (cmdItem) => ` + + `, + action: () => alert('Export to CSV'), + }, + { + command: 'refresh-data', + title: 'Refresh Data', + iconCssClass: 'mdi mdi-refresh', + // Demo: slotRenderer with keyboard shortcut + slotRenderer: (cmdItem) => { + // you can use `createDomElement()` from Slickgrid for easier DOM element creation + const menuItemElm = createDomElement('div', { className: 'menu-item' }); + const iconElm = createDomElement('i', { className: `${cmdItem.iconCssClass} menu-item-icon` }); + const menuItemLabelElm = createDomElement('span', { className: 'menu-item-label', textContent: cmdItem.title || '' }); + const kbdElm = createDomElement('kbd', { className: 'key-hint', textContent: 'F5' }); + menuItemElm.appendChild(iconElm); + menuItemElm.appendChild(menuItemLabelElm); + menuItemElm.appendChild(kbdElm); + return menuItemElm; + }, + action: () => alert('Refresh data'), + }, + ] as Array; + }, + }, + + // tooltip plugin + externalResources: [new SlickCustomTooltip()], + customTooltip: { + observeAllTooltips: true, + }, + }; + } + + clearGrouping() { + this.aureliaGrid?.dataView?.setGrouping([]); + } + + collapseAllGroups() { + this.aureliaGrid?.dataView?.collapseAllGroups(); + } + + expandAllGroups() { + this.aureliaGrid?.dataView?.expandAllGroups(); + } + + groupByDuration() { + // you need to manually add the sort icon(s) in UI + this.aureliaGrid?.slickGrid?.setSortColumns([{ columnId: 'duration', sortAsc: true }]); + this.aureliaGrid?.dataView?.setGrouping({ + getter: 'duration', + formatter: (g) => `Duration: ${g.value} (${g.count} items)`, + comparer: (a, b) => SortComparers.numeric(a.value, b.value, SortDirectionNumber.asc), + aggregators: [new Aggregators.Avg('percentComplete'), new Aggregators.Sum('cost')], + aggregateCollapsed: false, + lazyTotalsCalculation: true, + } as Grouping); + this.aureliaGrid?.slickGrid?.invalidate(); // invalidate all rows and re-render + } + + loadData(count: number): ReportItem[] { + const tmpData: ReportItem[] = []; + for (let i = 0; i < count; i++) { + const randomDuration = Math.round(Math.random() * 100); + const randomYear = 2000 + Math.floor(Math.random() * 10); + const randomMonth = Math.floor(Math.random() * 11); + const randomDay = Math.floor(Math.random() * 29); + const randomPercent = Math.round(Math.random() * 100); + + tmpData[i] = { + id: i, + title: 'Task ' + i, + duration: randomDuration, + cost: Math.round(Math.random() * 10000) / 100, + percentComplete: randomPercent, + start: new Date(randomYear, randomMonth, randomDay), + finish: new Date(randomYear, randomMonth + 1, randomDay), + }; + } + return tmpData; + } + + toggleSubTitle() { + this.hideSubTitle = !this.hideSubTitle; + const action = this.hideSubTitle ? 'add' : 'remove'; + document.querySelector('.subtitle')?.classList[action]('hidden'); + this.aureliaGrid.resizerService.resizeGrid(0); + } +} diff --git a/demos/aurelia/src/examples/slickgrid/example6.ts b/demos/aurelia/src/examples/slickgrid/example6.ts index 84d339945a..1214ebfec7 100644 --- a/demos/aurelia/src/examples/slickgrid/example6.ts +++ b/demos/aurelia/src/examples/slickgrid/example6.ts @@ -10,8 +10,6 @@ import { resolve } from 'aurelia'; import { Filters, Formatters, - OperatorType, - SortDirection, type AureliaGridInstance, type Column, type CursorPageInfo, @@ -182,7 +180,7 @@ export class Example6 { gridHeight: 200, gridWidth: 900, compoundOperatorAltTexts: { - // where '=' is any of the `OperatorString` type shown above + // where '=' is any of the `OperatorType` type shown above text: { Custom: { operatorAlt: '%%', descAlt: 'SQL Like' } }, }, gridMenu: { @@ -205,18 +203,18 @@ export class Example6 { ], filters: [ // you can use OperatorType or type them as string, e.g.: operator: 'EQ' - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, - // { columnId: 'name', searchTerms: ['John Doe'], operator: OperatorType.contains }, - { columnId: 'name', searchTerms: ['Joh*oe'], operator: OperatorType.startsWithEndsWith }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, + // { columnId: 'name', searchTerms: ['John Doe'], operator: 'Contains' }, + { columnId: 'name', searchTerms: ['Joh*oe'], operator: 'StartsWithEndsWith' }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }, // use a date range with 2 searchTerms values - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ], sorters: [ // direction can written as 'asc' (uppercase or lowercase) and/or use the SortDirection type { columnId: 'name', direction: 'asc' }, - { columnId: 'company', direction: SortDirection.DESC }, + { columnId: 'company', direction: 'DESC' }, ], pagination: { pageNumber: this.isWithCursor ? 1 : 2, pageSize: 20 }, // if cursor based, start at page 1 }, @@ -233,7 +231,7 @@ export class Example6 { }, ], filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { // technically speaking GraphQL isn't a database query language like SQL, it's an application query language. // What that means is that GraphQL won't let you write arbitrary queries out of the box. // It will only support the types of queries defined in your GraphQL schema. @@ -360,11 +358,11 @@ export class Example6 { // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.aureliaGrid.filterService.updateFilters([ - { columnId: 'gender', searchTerms: ['female'], operator: OperatorType.equal }, - { columnId: 'name', searchTerms: ['Jane'], operator: OperatorType.startsWith }, + { columnId: 'gender', searchTerms: ['female'], operator: '=' }, + { columnId: 'name', searchTerms: ['Jane'], operator: 'StartsWith' }, { columnId: 'company', searchTerms: ['acme'], operator: 'IN' }, - { columnId: 'billingAddressZip', searchTerms: ['11'], operator: OperatorType.greaterThanOrEqual }, - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'billingAddressZip', searchTerms: ['11'], operator: '>=' }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ]); } @@ -383,18 +381,18 @@ export class Example6 { this.aureliaGrid.filterService.updateFilters([ // you can use OperatorType or type them as string, e.g.: operator: 'EQ' - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, - // { columnId: 'name', searchTerms: ['John Doe'], operator: OperatorType.contains }, - { columnId: 'name', searchTerms: ['Joh*oe'], operator: OperatorType.startsWithEndsWith }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, + // { columnId: 'name', searchTerms: ['John Doe'], operator: 'Contains' }, + { columnId: 'name', searchTerms: ['Joh*oe'], operator: 'StartsWithEndsWith' }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }, // use a date range with 2 searchTerms values - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ]); this.aureliaGrid.sortService.updateSorting([ // direction can written as 'asc' (uppercase or lowercase) and/or use the SortDirection type { columnId: 'name', direction: 'asc' }, - { columnId: 'company', direction: SortDirection.DESC }, + { columnId: 'company', direction: 'DESC' }, ]); setTimeout(() => { this.aureliaGrid.paginationService?.changeItemPerPage(20); diff --git a/demos/aurelia/src/examples/slickgrid/example9.ts b/demos/aurelia/src/examples/slickgrid/example9.ts index 6532536593..e2f495c0f4 100644 --- a/demos/aurelia/src/examples/slickgrid/example9.ts +++ b/demos/aurelia/src/examples/slickgrid/example9.ts @@ -1,7 +1,6 @@ import { I18N } from '@aurelia/i18n'; import { resolve } from 'aurelia'; import { - ExtensionName, Filters, Formatters, type AureliaGridInstance, @@ -297,7 +296,7 @@ export class Example9 { toggleGridMenu(e: MouseEvent) { if (this.aureliaGrid?.extensionService) { - const gridMenuInstance = this.aureliaGrid.extensionService.getExtensionInstanceByName(ExtensionName.gridMenu); + const gridMenuInstance = this.aureliaGrid.extensionService.getExtensionInstanceByName('gridMenu'); // open the external button Grid Menu, you can also optionally pass Grid Menu options as 2nd argument // for example we want to align our external button on the right without affecting the menu within the grid which will stay aligned on the left gridMenuInstance.showGridMenu(e, { dropSide: 'right' }); diff --git a/demos/aurelia/src/examples/slickgrid/utilities.ts b/demos/aurelia/src/examples/slickgrid/utilities.ts index b4c1bff6ef..ae78294c55 100644 --- a/demos/aurelia/src/examples/slickgrid/utilities.ts +++ b/demos/aurelia/src/examples/slickgrid/utilities.ts @@ -30,13 +30,6 @@ export function showToast(msg: string, type: 'danger' | 'info' | 'warning', time }, time); return; } - - // @deprecated, remove fallback in next major release - // otherwise, fallback (when popover is not supported): keep the div visible as regular HTML and remove after timeout. - divContainer.style.left = '50%'; - divContainer.style.top = '20px'; - divContainer.style.transform = 'translateX(-50%)'; - setTimeout(() => divContainer.remove(), time); } export function zeroPadding(input: string | number) { diff --git a/demos/aurelia/src/my-app.ts b/demos/aurelia/src/my-app.ts index 933828aa59..b2bd69762c 100644 --- a/demos/aurelia/src/my-app.ts +++ b/demos/aurelia/src/my-app.ts @@ -57,6 +57,7 @@ const myRoutes: Routeable[] = [ { path: 'example48', component: () => import('./examples/slickgrid/example48.js'), title: '48- Hybrid Selection Model' }, { path: 'example49', component: () => import('./examples/slickgrid/example49.js'), title: '49- Spreadsheet Drag-Fill' }, { path: 'example50', component: () => import('./examples/slickgrid/example50.js'), title: '50- Master/Detail Grids' }, + { path: 'example51', component: () => import('./examples/slickgrid/example51.js'), title: '51- Menus with Slots' }, { path: 'home', component: () => import('./home-page.js'), title: 'Home' }, ]; @route({ diff --git a/demos/aurelia/test/cypress/e2e/example10.cy.ts b/demos/aurelia/test/cypress/e2e/example10.cy.ts index 06d6048e1d..22544dc7ab 100644 --- a/demos/aurelia/test/cypress/e2e/example10.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example10.cy.ts @@ -46,11 +46,8 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should have 2 rows (Task 12,Task 13) selected in 2nd grid after typing in a search filter', () => { cy.get('#grid2').find('.filter-title').type('Task 1'); - cy.get('#grid2').find('.slick-row').should('not.have.length', 0); - cy.get('[data-test=grid2-selections]').should('contain', ''); - cy.get('#grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); @@ -141,112 +138,72 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { .then((pageNumber) => expect(pageNumber).to.eq('2')); cy.get('@grid1').find('[data-test=page-count]').contains('99'); - cy.get('@grid1').find('[data-test=item-from]').contains('6'); - cy.get('@grid1').find('[data-test=item-to]').contains('10'); - cy.get('@grid1').find('[data-test=total-items]').contains('495'); // 2nd Grid cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('5'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); }); it('should change Page Number in Grid1 and expect the Pagination to have correct values', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('[data-test=page-number-input]').clear().type('52').type('{enter}'); - cy.get('@grid1').find('[data-test=page-count]').contains('99'); - cy.get('@grid1').find('[data-test=item-from]').contains('256'); - cy.get('@grid1').find('[data-test=item-to]').contains('260'); - cy.get('@grid1').find('[data-test=total-items]').contains('495'); }); it('should change Page Number and Page Size in Grid2 and expect the Pagination to have correct values', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('34').type('{enter}'); - cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('166'); - cy.get('@grid2').find('[data-test=item-to]').contains('170'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); - cy.get('@grid2').find('#items-per-page-label').select('75'); - cy.get('@grid2').find('[data-test=page-count]').contains('7'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('75'); }); it('should go back to Grid1 and expect the same value before changing Pagination of Grid2', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('[data-test=page-count]').contains('99'); - cy.get('@grid1').find('[data-test=item-from]').contains('256'); - cy.get('@grid1').find('[data-test=item-to]').contains('260'); - cy.get('@grid1').find('[data-test=total-items]').contains('495'); }); it('should display page 0 of 0 with 0 items when applied filter returning an empty dataset', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('.filter-title').type('000'); - cy.get('.slick-empty-data-warning:visible').contains('No data to display.'); - cy.get('@grid1').find('[data-test=page-count]').contains('0'); - cy.get('@grid1').find('[data-test=item-from]').should('not.be.visible'); - cy.get('@grid1').find('[data-test=item-to]').should('not.be.visible'); - cy.get('@grid1').find('[data-test=total-items]').contains('0'); }); it('should erase part of the filter to have "00" and expect 4 items in total with 1 page', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('.filter-title').type('{backspace}'); - cy.get('.slick-empty-data-warning').contains('No data to display.').should('not.be.visible'); - cy.get('@grid1').find('[data-test=page-count]').contains('1'); - cy.get('@grid1').find('[data-test=item-from]').contains('1'); - cy.get('@grid1').find('[data-test=item-to]').contains('4'); - cy.get('@grid1').find('[data-test=total-items]').contains('4'); }); it('should also expect Grid2 to be unchanged (after changing Pagination in Grid1 in previous tests)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('[data-test=page-count]').contains('7'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('75'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); }); @@ -265,35 +222,24 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should go to Page 3 of 2nd Grid and have 2 rows selected in that Page and also have 4 rows selected in the entire grid (Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('#items-per-page-label').select('5'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('3').type('{enter}'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should go to last Page of 2nd Grid and have 1 rows selected in that Page and also have 4 rows selected in the entire grid (Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.icon-seek-end').click(); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 1); }); it(`should go to first Page of 2nd Grid and select another row (Task 1) in that Page, wich will now be (Task1,Task3) and now have 5 rows selected in the entire grid (Task 1,Task 3,Task 12,Task 13,Task 522)`, () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.icon-seek-first').click().wait(10); - cy.get('@grid2').find('.slick-row:nth(1) .slick-cell:nth(0) input[type=checkbox]').click({ force: true }); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); cy.window().then((win) => { @@ -313,23 +259,16 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should go back to Page 3 of 2nd Grid and have 2 rows selected in that Page and also retain 5 selected rows in the entire grid (Task 1,Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('#items-per-page-label').select('5'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('3').type('{enter}'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should go to last Page of 2nd Grid and still have 1 row selected in that Page and also retain 5 selected rows in the entire grid (Task 1,Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.icon-seek-end').click(); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 1); }); }); @@ -366,27 +305,18 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should go to Page 61 of Grid1 and expect to find "Task 300" still be selected', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('[data-test=page-number-input]').clear().type('61').type('{enter}'); - cy.get('[data-test=grid1-selections]').contains('Task 300'); - cy.get('.slick-cell.l0.r0.slick-cell-checkboxsel.selected').should('exist'); - cy.get('[data-test=grid1-selections]').contains('Task 300'); }); it('should go to a different page for next test to confirm that it will then go to page 1', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('22').type('{enter}'); - cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('106'); - cy.get('@grid2').find('[data-test=item-to]').contains('110'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); }); @@ -453,35 +383,26 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should have 5 rows (Task 1,Task 3,Task 12,Task 13,Task 522) selected in the entire 2nd grid BUT only 2 shown in the DOM in the top portion of the grid (because SlickGrid uses virtual rendering)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should scroll to the bottom of 2nd Grid and still have 5 rows (Task 1,Task 3,Task 12,Task 13,Task 522) selected and find 2 row selected because we now have 2 rows that got rendered (first and last)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(10); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should have 2 rows (Task 3,Task 13) selected in 2nd grid after typing in a search filter (3)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.filter-title').type('3'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(10); - cy.get('@grid2').find('.slick-row').should('not.have.length', 0); cy.wait(50); cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 13'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); cy.window().then((win) => { @@ -514,9 +435,7 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { describe('Re-enable Pagination', () => { it('should re-enable the Pagination and expect to see it show it again below the grid at Page 1', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=toggle-pagination-grid2]').click(); - cy.get('#slickGridContainer-grid2 .slick-pagination').should('exist'); cy.get('@grid2') @@ -525,13 +444,9 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { .then((pageNumber) => expect(pageNumber).to.eq('1')); cy.get('@grid2').find('[data-test=page-number-input]').click(); - cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('5'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); cy.window().then((win) => { @@ -549,13 +464,9 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should have 2 rows (Task 3,Task 13) selected in 2nd grid after typing in a search filter (3)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.filter-title').type('3'); - cy.get('@grid2').find('.slick-row').should('not.have.length', 0); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 13'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); cy.window().then((win) => { @@ -583,11 +494,8 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { .then((pageNumber) => expect(pageNumber).to.eq('1')); cy.get('@grid2').find('[data-test=page-count]').contains('3'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('5'); - cy.get('@grid2').find('[data-test=total-items]').contains('179'); }); }); diff --git a/demos/aurelia/test/cypress/e2e/example14.cy.ts b/demos/aurelia/test/cypress/e2e/example14.cy.ts index 402545254b..91f7db34b6 100644 --- a/demos/aurelia/test/cypress/e2e/example14.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example14.cy.ts @@ -21,16 +21,16 @@ describe('Example 14 - Column Span & Header Grouping', () => { }); it('should have a frozen grid on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 2); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 3); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]`).children().should('have.length', 4); + cy.get('#grid2').find('[data-row=0]').should('have.length', 2); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]> .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]> .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]> .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]> .slick-cell:nth(0)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]> .slick-cell:nth(1)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(1)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid again', () => { @@ -48,14 +48,14 @@ describe('Example 14 - Column Span & Header Grouping', () => { it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { cy.get('[data-test="remove-frozen-column-button"]').click(); - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 1); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 7); + cy.get('#grid2').find('[data-row=0]').should('have.length', 1); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid once again', () => { @@ -73,16 +73,16 @@ describe('Example 14 - Column Span & Header Grouping', () => { it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { cy.contains('Set 3 Frozen Columns').click({ force: true }); - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 2); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 3); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]`).children().should('have.length', 4); + cy.get('#grid2').find('[data-row=0]').should('have.length', 2); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0] > .slick-cell:nth(0)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0] > .slick-cell:nth(1)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(1)').should('contain', '01/05/2009'); }); it('should have still exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -102,14 +102,14 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.contains('Unfreeze Columns/Rows').click({ force: true }); - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 1); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 7); + cy.get('#grid2').find('[data-row=0]').should('have.length', 1); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); }); it('should reapply 3 frozen columns on 2nd grid', () => { @@ -209,7 +209,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { }); describe('Colspan checks on 1st grid', () => { - it('should hide Finish column and still expect "5 days" to spread accross 3 column', () => { + it('should hide Finish column and expect "5 days" spread to drop from 3 to 2 columns (1x hidden column)', () => { cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').should('contain', 'Task 1'); cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r5').should('contain', 'Task 2'); cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r3').should('contain', '5 days'); @@ -232,11 +232,64 @@ describe('Example 14 - Column Span & Header Grouping', () => { .should('contain', 'Hide Column') .click(); + // goto right + cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').click(); + cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0.active').should('contain', 'Task 1').type('{rightArrow}'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r3.active').should('contain', '5 days').type('{rightArrow}'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l4.r4.active').contains(/\d+$/).type('{rightArrow}'); + cy.get('#grid1') + .find('[data-row=1] .slick-cell.l5.r5') + .contains(/(true|false)+$/); + + // goto left + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active') + .contains(/(true|false)+$/) + .type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').contains(/\d+$/).type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3.active').should('contain', '5 days').type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0.active').should('contain', 'Task 1'); + }); + + it('should reset Column Picker, click on Spread Hidden Coolumn button then hide Finish column and still expect "5 days" to spread accross 3 column', () => { + cy.get('#grid1').find('.slick-header-column:nth(1)').trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children('li:nth-child(4)') + .children('label') + .should('contain', 'Period - Finish') + .click(); + + cy.get('.slick-column-picker .close').click(); + + cy.get('[data-test="spread-colspan-button"]').click(); cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').should('contain', 'Task 1'); - cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r4').should('contain', 'Task 2'); + cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r5').should('contain', 'Task 2'); cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r3').should('contain', '5 days'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l4.r4').contains(/\d+$/); + cy.get('#grid1') + .find('[data-row=1] .slick-cell.l5.r5') + .contains(/(true|false)+$/); + + cy.get('#grid1') + .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('.slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Hide Column') + .click(); + + cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').should('contain', 'Task 1'); + cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r5').should('contain', 'Task 2'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r4').should('contain', '5 days'); cy.get('#grid1') - .find('[data-row=1] .slick-cell.l4.r4') + .find('[data-row=1] .slick-cell.l5.r5') .contains(/(true|false)+$/); }); @@ -292,15 +345,13 @@ describe('Example 14 - Column Span & Header Grouping', () => { describe('First Grid - Key Navigation', () => { it('should start at Task 1 and expect "Duration" to have colspan of 3 and show "% Complete" and "Effort Driven"', () => { cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1').click().type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days').type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4').should('have.class', 'active').contains(/\d+$/).type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5') - .should('have.class', 'active') - .contains(/(true|false)+$/); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3.active').should('contain', '5 days').type('{rightArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').contains(/\d+$/).type('{rightArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active').contains(/(true|false)+$/); cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active').type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4').should('have.class', 'active').contains(/\d+$/).type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days'); + cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').contains(/\d+$/).type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3.active').should('contain', '5 days'); cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1'); }); @@ -312,16 +363,17 @@ describe('Example 14 - Column Span & Header Grouping', () => { .children('label') .should('contain', 'Period - Finish') .click(); + cy.get('.slick-column-picker .close').click(); cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1').click().type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days').type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4') + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r4.active').should('contain', '5 days').type('{rightArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5') .should('have.class', 'active') .contains(/(true|false)+$/); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days').type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('have.class', 'active'); + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active').type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r4.active').should('contain', '5 days').type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0.active'); cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1'); }); }); diff --git a/demos/aurelia/test/cypress/e2e/example16.cy.ts b/demos/aurelia/test/cypress/e2e/example16.cy.ts index a1a2073bb5..78dabd7ae7 100644 --- a/demos/aurelia/test/cypress/e2e/example16.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example16.cy.ts @@ -408,7 +408,7 @@ describe('Example 16 - Row Move & Checkbox Selector Selector Plugins', () => { }); it('should add Edit/Delete columns and expect 2 new columns added at the beginning of the grid', () => { - const newExpectedColumns = ['', '', ...fullTitles]; + const newExpectedColumns = ['', '', '', '', 'Title', '% Complete', 'Start', 'Finish', 'Duration', 'Completed']; cy.get('[data-test="add-crud-columns-btn"]').click(); cy.get('#grid16') diff --git a/demos/aurelia/test/cypress/e2e/example18.cy.ts b/demos/aurelia/test/cypress/e2e/example18.cy.ts index 745a31f7e4..eae744afce 100644 --- a/demos/aurelia/test/cypress/e2e/example18.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example18.cy.ts @@ -122,11 +122,10 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { it('should expand all rows with "Expand All" from context menu and expect all the Groups to be expanded and the Toogle All icon to be collapsed', () => { cy.get('#grid18').find('.slick-row .slick-cell:nth(1)').rightclick({ force: true }); - cy.get('.slick-context-menu .slick-menu-command-list') - .find('.slick-menu-item') - .find('.slick-menu-content') - .contains('Expand all Groups') - .click(); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Export in CSV format'); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Export to Excel'); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Export to PDF'); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Expand all Groups').click(); cy.get('#grid18').find('.slick-group-toggle.collapsed').should('have.length', 0); diff --git a/demos/aurelia/test/cypress/e2e/example32.cy.ts b/demos/aurelia/test/cypress/e2e/example32.cy.ts index c2c8d5f769..1c8eb7b69e 100644 --- a/demos/aurelia/test/cypress/e2e/example32.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example32.cy.ts @@ -181,7 +181,12 @@ describe('Example 32 - Columns Resize by Content', () => { const yesterdayDate = format(addDay(new Date(), -1), 'YYYY-MM-DD'); const todayDate = format(new Date(), 'YYYY-MM-DD'); - cy.get(`[data-vc-date=${yesterdayDate}]`).should('have.attr', 'data-vc-date-disabled'); + // Check if yesterday's date element exists (may not be visible when 1st day of the month is a Sunday, e.g. 2026-02-01) + cy.get(`[data-vc-date=${yesterdayDate}]`).then(($el) => { + if ($el.length > 0) { + expect($el).to.have.attr('data-vc-date-disabled'); + } + }); cy.get(`[data-vc-date=${todayDate}]`).should('not.have.attr', 'data-vc-date-disabled'); // make grid readonly again @@ -379,16 +384,21 @@ describe('Example 32 - Columns Resize by Content', () => { }); it('should be able to edit "Duration" when "autoEditByKeypress" is enabled and by clicking once on second row and expect next row to become editable', () => { - cy.get('[data-row="2"] .slick-cell.l2.r2').contains(/[0-9]* days/); - cy.get('[data-row="2"] .slick-cell.l2.r2').click(); - cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); - - cy.get('[data-row="2"] .slick-cell.l2.r2').type('123'); - cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 1); - cy.get('[data-row="2"] .slick-cell.l2.r2').type('{enter}'); - cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); - - cy.get('[data-row="2"] .slick-cell.l2.r2').should('contain', '123 days'); + // Check if yesterday's date element exists (may not be visible when we run the test on the 1st day of the month and it is a Sunday, e.g. 2026-02-01) + cy.get('[data-row="2"] .slick-cell.l2.r2').then(($el) => { + if ($el.length > 0) { + cy.wrap($el).contains(/[0-9]* days/); + cy.wrap($el).click(); + cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); + + cy.get('[data-row="2"] .slick-cell.l2.r2').type('123'); + cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 1); + cy.get('[data-row="2"] .slick-cell.l2.r2').type('{enter}'); + cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); + + cy.get('[data-row="2"] .slick-cell.l2.r2').should('contain', '123 days'); + } + }); }); it('should click on "Auto-Edit by keyboard OFF" button', () => { diff --git a/demos/aurelia/test/cypress/e2e/example33.cy.ts b/demos/aurelia/test/cypress/e2e/example33.cy.ts index fe0cec43d7..d4b06f03f6 100644 --- a/demos/aurelia/test/cypress/e2e/example33.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example33.cy.ts @@ -5,6 +5,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { 'Duration', 'Description', 'Description 2', + 'Button Tooltip', 'Cost', '% Complete', 'Start', @@ -13,7 +14,6 @@ describe('Example 33 - Regular & Custom Tooltips', () => { 'Prerequisites', 'Action', ]; - const GRID_ROW_HEIGHT = 33; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example33`); @@ -32,7 +32,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over 1st row checkbox column and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0)`).as('checkbox0-cell'); + cy.get('[data-row="0"] > .slick-cell:nth(0)').as('checkbox0-cell'); cy.get('@checkbox0-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); @@ -40,7 +40,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over Task 2 cell and expect async tooltip to show', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(1)`).as('task1-cell'); + cy.get('[data-row="0"] > .slick-cell:nth(1)').as('task1-cell'); cy.get('@task1-cell').should('contain', 'Task 2'); cy.get('@task1-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').contains('loading...'); @@ -64,7 +64,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over Task 6 cell and expect async tooltip to show', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(1)`).as('task6-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(1)').as('task6-cell'); cy.get('@task6-cell').should('contain', 'Task 6'); cy.get('@task6-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').contains('loading...'); @@ -86,7 +86,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { it('should mouse over Task 6 cell on "Start" column and expect a delayed tooltip opening via async process', () => { cy.get('.slick-custom-tooltip').should('not.exist'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(7)`).as('start6-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(8)').as('start6-cell'); cy.get('@start6-cell').contains(/\d{4}-\d{2}-\d{2}$/); // use regexp to make sure it's a number cy.get('@start6-cell').trigger('mouseover'); @@ -110,7 +110,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over 6th row Description and expect full cell content to show in a tooltip because cell has ellipsis and is too long for the cell itself', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(3)`).as('desc6-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(3)').as('desc6-cell'); cy.get('@desc6-cell').should('contain', 'This is a sample task description.'); cy.get('@desc6-cell').trigger('mouseover'); @@ -125,7 +125,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over 6th row Description 2 and expect regular tooltip title + concatenated full cell content when using "useRegularTooltipFromFormatterOnly: true"', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(4)`).as('desc2-5-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(4)').as('desc2-5-cell'); cy.get('@desc2-5-cell').should('contain', 'This is a sample task description.'); cy.get('@desc2-5-cell').trigger('mouseover'); @@ -138,8 +138,24 @@ describe('Example 33 - Regular & Custom Tooltips', () => { cy.get('@desc2-5-cell').trigger('mouseout'); }); + it('should mouse over Button Tooltip column and verify button and icon tooltips show correctly', () => { + cy.get('[data-row="2"] > .slick-cell:nth(5)').as('button-cell'); + + // Hover over the button element and expect its tooltip + cy.get('@button-cell').find('button').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip').should('contain', 'This is the button tooltip'); + cy.get('@button-cell').find('button').trigger('mouseout'); + + // Hover over the icon inside the button and expect its tooltip + cy.get('@button-cell').find('i.mdi').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip').should('contain', 'icon tooltip'); + cy.get('@button-cell').find('i.mdi').trigger('mouseout'); + }); + it('should mouse over 2nd row Duration and expect a custom tooltip shown with 4 label/value pairs displayed', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(2)`).as('duration2-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(2)').as('duration2-cell'); cy.get('@duration2-cell').contains(/\d+\sday[s]?$/); cy.get('@duration2-cell').trigger('mouseover'); @@ -162,7 +178,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over % Complete cell of Task 6 and expect regular tooltip to show with content "x %" where x is a number', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(6)`).as('percentage-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(7)').as('percentage-cell'); cy.get('@percentage-cell').find('.percent-complete-bar').should('exist'); cy.get('@percentage-cell').trigger('mouseover'); @@ -173,7 +189,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over Prerequisite cell of Task 6 and expect regular tooltip to show with content "Task 6, Task 5"', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(10)`).as('prereq-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(11)').as('prereq-cell'); cy.get('@prereq-cell').should('contain', 'Task 6, Task 5'); cy.get('@prereq-cell').trigger('mouseover'); @@ -205,7 +221,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over header-row (filter) Finish column and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(8)`).as('finish-filter'); + cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(9)`).as('finish-filter'); cy.get('@finish-filter').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); @@ -213,7 +229,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should open PreRequisite dropdown and expect it be lazily loaded', () => { - cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(10)').as('checkbox10-header'); + cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(11)').as('checkbox10-header'); cy.get('@checkbox10-header').click(); cy.get('[data-test="alert-lazy"]').should('be.visible'); cy.get('[data-name="filter-prerequisites"] .ms-loading span').contains('Loading...'); @@ -224,7 +240,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over header-row (filter) Prerequisite column and expect to see tooltip of selected filter options', () => { - cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(10)').as('checkbox10-header'); + cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(11)').as('checkbox10-header'); cy.get('@checkbox10-header').trigger('mouseover'); cy.get('.filter-prerequisites .ms-choice span').contains('15 of 1000 selected'); @@ -258,25 +274,57 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over header title on 2nd column with Finish name and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get('.slick-header-columns .slick-header-column:nth(8)').as('finish-header'); + cy.get('.slick-header-columns .slick-header-column:nth(9)').as('finish-header'); cy.get('@finish-header').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); cy.get('@finish-header').trigger('mouseout'); }); + it('should mouse over "Filters Empty Description" button and expect global tooltip to show with title text', () => { + // Test button tooltip + cy.get('[data-test="filter-empty-desc"]').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'Apply filter to show only empty descriptions'); + cy.get('[data-test="filter-empty-desc"]').trigger('mouseout'); + + // Test icon tooltip + cy.get('[data-test="filter-empty-desc"] i.mdi').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'icon tooltip for empty descriptions'); + cy.get('[data-test="filter-empty-desc"] i.mdi').trigger('mouseout'); + + // Verify tooltip is hidden when hovering on another element + cy.get('[data-test="server-delay"]').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('not.exist'); + }); + + it('should mouse over "Filters Non-Empty Description" button and expect global tooltip to show with title text', () => { + // Test button tooltip + cy.get('[data-test="filter-non-empty-desc"]').trigger('mouseover'); + cy.wait(50); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'Apply filter to show only non-empty descriptions'); + cy.get('[data-test="filter-non-empty-desc"]').trigger('mouseout'); + + // Test icon tooltip + cy.get('[data-test="filter-non-empty-desc"] i.mdi').trigger('mouseover'); + cy.wait(10); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'icon tooltip for non-empty descriptions'); + cy.get('[data-test="filter-non-empty-desc"] i.mdi').trigger('mouseout'); + cy.wait(10); + cy.get('.slick-custom-tooltip').should('not.exist'); + }); + it('should click Prerequisite editor of 1st row (Task 2) and expect Task1 & 2 to be selected in the multiple-select drop', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(10)`).as('prereq-cell'); + cy.get('[data-row="0"] > .slick-cell:nth(11)').as('prereq-cell'); cy.get('@prereq-cell').should('contain', 'Task 2, Task 1').click(); cy.get('div.ms-drop[data-name=editor-prerequisites]').find('li.selected').should('have.length', 2); - cy.get('div.ms-drop[data-name=editor-prerequisites]').find('li.selected:nth(0) span').should('contain', 'Task 1'); - cy.get('div.ms-drop[data-name=editor-prerequisites]').find('li.selected:nth(1) span').should('contain', 'Task 2'); - cy.get('div.ms-drop[data-name=editor-prerequisites]').find('.ms-ok-button').click(); - cy.get('div.ms-drop[data-name=editor-prerequisites]').should('not.exist'); }); }); diff --git a/demos/aurelia/test/cypress/e2e/example44.cy.ts b/demos/aurelia/test/cypress/e2e/example44.cy.ts index 6f70275d7a..f399ed0364 100644 --- a/demos/aurelia/test/cypress/e2e/example44.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example44.cy.ts @@ -463,4 +463,102 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { cy.get('[data-row=499] > .slick-cell.l5.r5.active').should('have.length', 1); }); }); + + describe('Hide Columns with colspan/rowspan', () => { + it('should hide Title column and expect other colspan/rowspan to simply move over and stay attached to same columns', () => { + cy.get('[data-row=499] > .slick-cell.l5.r5.active').type('{ctrl}{home}', { release: false }); + cy.get('[data-row=2] > .slick-cell.l0.r0.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + + cy.get('.slick-header-column:nth(1)').trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children('li:nth-child(1)') + .children('label') + .should('contain', 'Title') + .click(); + cy.get('.slick-column-picker .close').click(); + + // Task 2 rowspan should be hidden now + cy.get('[data-row=2] > .slick-cell.l0.r0.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.not.eq(GRID_ROW_HEIGHT * 3) + ); + }); + + it('should start at "Revenue Growth" second cell down, then type "Arrow Right" key 2x times and expect 4th row "Policy Index" green section to still have a rowspan 3x and colspan of 4x', () => { + cy.get('[data-row=3] > .slick-cell.l1.r1.rowspan').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l1.r1.active').should('have.length', 1); + cy.get('@active_cell').type('{rightarrow}{rightarrow}'); + + cy.get('[data-row=2] > .slick-cell.l3.r5').should('not.have.class', 'rowspan'); + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + }); + + it('should go up by 1x "Arrow Up" and expect blue section colspan of 3x', () => { + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').type('{uparrow}'); + cy.get('[data-row=2] > .slick-cell.l3.r5.active').should('not.have.class', 'rowspan'); + }); + + it('should "Revenue Growth" rowspan should now be at first column and "Policy Index" should now be at third column', () => { + cy.get(`[data-row=0] > .slick-cell.l0.r0`).should('not.exist'); + cy.get(`[data-row=1] > .slick-cell.l0.r0`).should('not.exist'); + cy.get(`[data-row=2] > .slick-cell.l0.r0`).should('not.exist'); + + cy.get('[data-row=0] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=3] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) + ); + cy.get('[data-row=8] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 80) + ); + + cy.get('[data-row=2] > .slick-cell.l3.r5').should('not.have.class', 'rowspan'); + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=8] > .slick-cell.l3.r4.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 492) + ); + }); + + it('should show again "Title" column and expect "Revenue Growth" and "Policy Index" columns to be moved to the right by 1 column', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children('li:nth-child(1)') + .children('label') + .should('contain', 'Title') + .click(); + cy.get('.slick-column-picker .close').click(); + + cy.get(`[data-row=0] > .slick-cell.l0.r0`).should('contain', 'Task 0'); + cy.get(`[data-row=1] > .slick-cell.l0.r0`).should('contain', 'Task 1'); + cy.get(`[data-row=2] > .slick-cell.l0.r0`).should('contain', 'Task 2'); + + cy.get('[data-row=0] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=3] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) + ); + cy.get('[data-row=8] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 80) + ); + + cy.get('[data-row=2] > .slick-cell.l3.r5').should('not.have.class', 'rowspan'); + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=8] > .slick-cell.l3.r4.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 492) + ); + }); + }); }); diff --git a/demos/aurelia/test/cypress/e2e/example48.cy.ts b/demos/aurelia/test/cypress/e2e/example48.cy.ts index a9df9df6b5..40d990bb34 100644 --- a/demos/aurelia/test/cypress/e2e/example48.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example48.cy.ts @@ -160,19 +160,17 @@ describe('Example 48 - Hybrid Selection Model', () => { }); it('should click on row 4 and 5 row checkbox and expect 5 full rows to be selected', () => { - cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l0.r0').as('task4'); cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l1.r1').should('contain', '4'); - cy.get('@task4').click(); + cy.get('#grid48-2 .slick-row[data-row="4"] input[type=checkbox]').click({ force: true }); cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 1); // select another row - cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l0.r0').as('task5'); cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l1.r1').should('contain', '5'); - cy.get('@task5').click(); + cy.get('#grid48-2 .slick-row[data-row="5"] input[type=checkbox]').click({ force: true }); cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); - cy.get('@task5').should('have.class', 'selected'); + cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 2); }); }); diff --git a/demos/aurelia/test/cypress/e2e/example51.cy.ts b/demos/aurelia/test/cypress/e2e/example51.cy.ts new file mode 100644 index 0000000000..80e5148c97 --- /dev/null +++ b/demos/aurelia/test/cypress/e2e/example51.cy.ts @@ -0,0 +1,382 @@ +import { format } from '@formkit/tempo'; + +describe('Example 51 - Menus with Slots', () => { + const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Action']; + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/example51`); + cy.get('h2').should('contain', 'Example 51: Menus with Slots'); + }); + + it('should have exact column titles in the grid', () => { + cy.get('#grid51') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should open Context Menu hover "Duration" column and expect built-in and custom items listed in specific order', () => { + cy.get('[data-row="0"] > .slick-cell:nth(2)').rightclick({ force: true }); + + // 1st item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('.edit-cell-icon').contains('โœŽ'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item span').contains('Edit Cell'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('kbd.edit-cell').contains('F2'); + + // icon should rotate while hovering + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0)').trigger('mouseover'); + cy.wait(175); // wait for rotation + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item .edit-cell-icon') + .invoke('css', 'transform') // Get the transform property + .then((cssTransform) => { + const transformValue = cssTransform as unknown as string; // Cast to string + cy.getTransformValue(transformValue, true, 'rotate').then((rotationAngle) => { + expect(rotationAngle).to.approximately(13, 15); // 15 degrees rotation + }); + }); + + // 2nd item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item').find('i.mdi-content-copy').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item').find('span.menu-item-label').contains('Copy'); + + // 3rd item - divider + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(2)').should('have.class', 'slick-menu-item-divider'); + + // 4th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(3)').should('have.class', 'slick-menu-item-disabled'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-close').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item') + .find('span.menu-item-label') + .contains('Clear all Grouping'); + + // 5th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-disabled'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item').find('i.mdi-arrow-collapse').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item') + .find('span.menu-item-label') + .contains('Collapse all Groups'); + + // 6th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(5)').should('have.class', 'slick-menu-item-disabled'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item').find('i.mdi-arrow-expand').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('span.menu-item-label') + .contains('Expand all Groups'); + + // 7th item - divider + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(6)').should('have.class', 'slick-menu-item-divider'); + + // 8th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item').find('i.mdi-download').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item') + .find('span.menu-item-label') + .contains('Export'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(7)').find('.sub-item-chevron').should('exist'); + + // 9th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(8)').should('have.class', 'slick-menu-item-divider'); + + // 10th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(9) .menu-item') + .find('i.mdi-delete.text-danger') + .should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(9) .menu-item') + .find('span.menu-item-label') + .contains('Delete Row'); + }); + + it('should open Export->Excel context sub-menu', () => { + const subCommands1 = ['Export as Excel', 'Export as CSV', 'Export as PDF']; + + const stub = cy.stub(); + cy.on('window:alert', stub); + + cy.get('[data-row="0"] > .slick-cell:nth(2)').should('contain', '0'); + cy.get('[data-row="0"] > .slick-cell:nth(2)').rightclick({ force: true }); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains(/^Export$/) + .trigger('mouseover'); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .menu-item') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + // click different sub-menu + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains('Export as Excel') + .should('exist') + .click() + .then(() => expect(stub.getCall(0)).to.be.calledWith('Export to Excel')); + + cy.get('.slick-submenu').should('have.length', 0); + }); + + it('should open Header Menu from the "Title" column and expect some commands to have keyboard hints on the right side', () => { + cy.get('.slick-header-column:nth(0)').trigger('mouseover', { force: true }); + cy.get('.slick-header-column:nth(0)').children('.slick-header-menu-button').invoke('show').click(); + + // 1st item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('i.mdi-arrow-expand-horizontal') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('span.menu-item-label') + .contains('Resize by Content'); + + // 2nd item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(1)').should('have.class', 'slick-menu-item-divider'); + + // 3rd item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('i.mdi-sort-ascending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item') + .find('span.menu-item-label') + .contains('Sort Ascending'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('kbd.key-hint').contains('Alt+โ†‘'); + + // 4th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-sort-descending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item') + .find('span.menu-item-label') + .contains('Sort Descending'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('kbd.key-hint').contains('Alt+โ†“'); + + // 5th item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-divider'); + + // 6th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('i.mdi-filter-remove-outline') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('span.menu-item-label') + .contains('Remove Filter'); + + // 7th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('i.mdi-sort-variant-off').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item') + .find('span.menu-item-label') + .contains('Remove Sort'); + + // 8th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item').find('i.mdi-close').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item') + .find('span.menu-item-label') + .contains('Hide Column'); + }); + + it('should open Header Menu from the "Duration" column and expect some commands to have tags on the right side', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover', { force: true }); + cy.get('.slick-header-column:nth(1)').children('.slick-header-menu-button').invoke('show').click(); + + // 1st item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('i.mdi-arrow-expand-horizontal') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('span.menu-item-label') + .contains('Resize by Content'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('span.key-hint.danger').contains('NEW'); + + // 2nd item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(1)').should('have.class', 'slick-menu-item-divider'); + + // 3rd item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('i.mdi-sort-ascending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item') + .find('span.menu-item-label') + .contains('Sort Ascending'); + + // 4th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-sort-descending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item') + .find('span.menu-item-label') + .contains('Sort Descending'); + + // 5th item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-divider'); + + // 6th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('i.mdi-filter-remove-outline') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('span.menu-item-label') + .contains('Remove Filter'); + + // 7th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('i.mdi-sort-variant-off').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item') + .find('span.menu-item-label') + .contains('Remove Sort'); + + // 8th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item').find('i.mdi-close').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item') + .find('span.menu-item-label') + .contains('Hide Column'); + }); + + it('should open Header Menu from the "Cost" column and expect first item to have a dynamic tooltip timestamp when hovering', () => { + cy.get('#grid51').find('.slick-header-column:nth(4)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + // 1st item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('.advanced-export-icon').contains('๐Ÿ“Š'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').contains('Advanced Export'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('kbd.key-hint').contains('Ctrl+E'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').should( + 'have.css', + 'background-color', + 'rgba(0, 0, 0, 0)' + ); + + // icon should scale up + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').trigger('mouseover'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item .advanced-export-icon') + .invoke('css', 'transform') + .then((cssTransform) => { + const transformValue = cssTransform as unknown as string; // Cast to string + cy.getTransformValue(transformValue, true, 'scale').then((scaleValue) => { + expect(scaleValue).to.be.approximately(1.1, 1.15); // Check the scale value if applied + }); + }); + + const today = format(new Date(), 'YYYY-MM-DD'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').should( + 'have.css', + 'background-color', + 'rgb(133, 70, 133)' + ); + cy.get('.slick-custom-tooltip').contains(`๐Ÿ“ˆ Export timestamp: ${today}`); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').trigger('mouseout'); + cy.get('.slick-custom-tooltip').should('not.exist'); + cy.get('body').click(); + }); + + it('should open Action Menu from last column "Action" column and expect custom items listed in specific order', () => { + cy.get('[data-row="1"] > .slick-cell:nth(6)').click(); + cy.get('.slick-command-header.with-title.with-close').contains('Cell Actions'); + + // 1st item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('.mdi-content-copy').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item span').contains('Copy Cell Value'); + + // 2nd item - divider + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(1)').should('have.class', 'slick-menu-item-divider'); + + // 3rd item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('.mdi-download').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item span').contains('Export Row'); + + // 4th item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-download').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('span.menu-item-label').contains('Export'); + + // 5th item - divider + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-divider'); + + // 6th item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item').find('.edit-cell-icon').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item .edit-cell-icon').contains('โœŽ'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(5)').should('have.css', 'background-color', 'rgba(0, 0, 0, 0)'); + + // 7th item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('.mdi-delete.text-danger').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item span').contains('Delete Row'); + }); + + it('should open Export->Excel cell sub-menu', () => { + const subCommands1 = ['Export as Excel', 'Export as CSV', 'Export as PDF']; + + const stub = cy.stub(); + cy.on('window:alert', stub); + + cy.get('.slick-cell-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains(/^Export$/) + .trigger('mouseover'); + + cy.get('.slick-cell-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .menu-item') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + // click different sub-menu + cy.get('.slick-cell-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains('Export as Excel') + .should('exist') + .click() + .then(() => expect(stub.getCall(0)).to.be.calledWith('Export row #1 to Excel')); + + cy.get('.slick-submenu').should('have.length', 0); + }); + + it('should open Grid Menu and expect built-in commands first then custom items listed in specific order', () => { + cy.get('.slick-grid-menu-button.mdi-menu').click(); + + // 1st item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('.mdi-filter-remove-outline.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item span').contains('Clear all Filters'); + + // 2nd item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item') + .find('.mdi-sort-variant-off.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item span').contains('Clear all Sorting'); + + // 3rd item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item') + .find('.mdi-flip-vertical.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item span').contains('Toggle Filter Row'); + + // 4th item - divider + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(3)').should('have.class', 'slick-menu-item-divider'); + + // 5th item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item') + .find('.mdi-file-excel-outline.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item span').contains('Export to Excel'); + + // 6th item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('.mdi-download.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item span').contains('Export to CSV'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item').find('span.key-hint.warn').contains('CUSTOM'); + + // 7th item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item') + .find('.mdi-refresh.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item span').contains('Refresh Data'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('kbd.key-hint').contains('F5'); + }); + + it('should sort ascending "Duration" even though the header menu item was override without an action callback', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').should('contain', 'Sort Ascending').click(); + + cy.get('[data-row=0]').children('.slick-cell:nth(1)').should('contain', '0'); + cy.get('[data-row=1]').children('.slick-cell:nth(1)').should('contain', '0'); + cy.get('[data-row=2]').children('.slick-cell:nth(1)').should('contain', '0'); + }); + + it('should sort descending "Duration" even though the header menu item was override without an action callback', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').should('contain', 'Sort Descending').click(); + + cy.get('[data-row=0]').children('.slick-cell:nth(1)').should('contain', '100'); + cy.get('[data-row=1]').children('.slick-cell:nth(1)').should('contain', '100'); + cy.get('[data-row=2]').children('.slick-cell:nth(1)').should('contain', '100'); + }); +}); diff --git a/demos/aurelia/test/cypress/support/commands.ts b/demos/aurelia/test/cypress/support/commands.ts index a35be6228b..3be5a3685b 100644 --- a/demos/aurelia/test/cypress/support/commands.ts +++ b/demos/aurelia/test/cypress/support/commands.ts @@ -47,6 +47,7 @@ declare global { ): Chainable>; saveLocalStorage: () => void; restoreLocalStorage: () => void; + getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable; } } } @@ -86,3 +87,35 @@ Cypress.Commands.add('restoreLocalStorage', () => { localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]); }); }); + +Cypress.Commands.add( + 'getTransformValue', + ( + cssTransformMatrix: string, + absoluteValue: boolean, + transformType: 'rotate' | 'scale' = 'rotate' // Default to 'rotate' + ): Cypress.Chainable => { + if (!cssTransformMatrix || cssTransformMatrix === 'none') { + throw new Error('Transform matrix is undefined or none'); + } + + const cssTransformMatrixIndexes = cssTransformMatrix.split('(')[1].split(')')[0].split(','); + + if (transformType === 'rotate') { + const cssTransformScale = Math.sqrt( + +cssTransformMatrixIndexes[0] * +cssTransformMatrixIndexes[0] + +cssTransformMatrixIndexes[1] * +cssTransformMatrixIndexes[1] + ); + + const cssTransformSin = +cssTransformMatrixIndexes[1] / cssTransformScale; + const cssTransformAngle = Math.round(Math.asin(cssTransformSin) * (180 / Math.PI)); + + return cy.wrap(absoluteValue ? Math.abs(cssTransformAngle) : cssTransformAngle); + } else if (transformType === 'scale') { + // Assuming scale is based on the first value in the matrix. + const scaleValue = +cssTransformMatrixIndexes[0]; // First value typically represents scaling in x direction. + return cy.wrap(scaleValue); // Directly return the scale value. + } + + throw new Error('Unsupported transform type'); + } +); diff --git a/demos/react/CHANGELOG.md b/demos/react/CHANGELOG.md index e5686b2b82..e6e4818a5d 100644 --- a/demos/react/CHANGELOG.md +++ b/demos/react/CHANGELOG.md @@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [10.0.0-beta.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v10.0.0-beta.0) (2026-02-14) + +### โš  BREAKING CHANGES + +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag (#2331) +* drop Cell/Row Selection Models & keep only HybridSelectionModel (#2330) +* remove all Deprecated code (#2302) +* drop OperatorType enums and keep only type literal (#2301) +* replacing multiple TypeScript `enum` with `type` to decrease build size (#2300) +* make Row Detail plugin as optional in all framework wrappers (#2291) +* switch to column `hidden` property and always keep all columns (#2281) + +### Features + +* add custom menu slot renderers ([#2375](https://github.com/ghiscoding/slickgrid-universal/issues/2375)) ([7ebbda5](https://github.com/ghiscoding/slickgrid-universal/commit/7ebbda58233bb5ce94b81b5b2b578af0ea6a068d)) - by @ghiscoding +* auto-enabled external resources with their associated flags ([#2362](https://github.com/ghiscoding/slickgrid-universal/issues/2362)) ([16dd8a7](https://github.com/ghiscoding/slickgrid-universal/commit/16dd8a77dd5d136a5a99321f0fc4c50571fdb0c0)) - by @ghiscoding +* drop Cell/Row Selection Models & keep only HybridSelectionModel ([#2330](https://github.com/ghiscoding/slickgrid-universal/issues/2330)) ([4398cf4](https://github.com/ghiscoding/slickgrid-universal/commit/4398cf42e03f6971b81db8cea4ed11138c0aa452)) - by @ghiscoding +* make Row Detail plugin as optional in all framework wrappers ([#2291](https://github.com/ghiscoding/slickgrid-universal/issues/2291)) ([fa1a14c](https://github.com/ghiscoding/slickgrid-universal/commit/fa1a14c16c987bfaf7725c46e8114b20ea5a505d)) - by @ghiscoding +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag ([#2331](https://github.com/ghiscoding/slickgrid-universal/issues/2331)) ([5be5e6a](https://github.com/ghiscoding/slickgrid-universal/commit/5be5e6a862ecd024cf43d404769d65c6c1dd335e)) - by @ghiscoding +* switch to column `hidden` property and always keep all columns ([#2281](https://github.com/ghiscoding/slickgrid-universal/issues/2281)) ([075c649](https://github.com/ghiscoding/slickgrid-universal/commit/075c64961cb7400500df46b792866d39fba2d9e0)) - by @ghiscoding +* **tooltip:** add global tooltip observation for non-grid elements ([#2371](https://github.com/ghiscoding/slickgrid-universal/issues/2371)) ([1bbc8de](https://github.com/ghiscoding/slickgrid-universal/commit/1bbc8de895e370843286eadd08574efc552ad8fd)) - by @ghiscoding + +### Bug Fixes + +* **deps:** update all non-major dependencies ([#2292](https://github.com/ghiscoding/slickgrid-universal/issues/2292)) ([21da957](https://github.com/ghiscoding/slickgrid-universal/commit/21da95702f44739a9f1b226e305c18a1fa64000f)) - by @renovate-bot +* **plugin:** SlickCustomTooltip should work with parent+child tooltips ([#2374](https://github.com/ghiscoding/slickgrid-universal/issues/2374)) ([8af7f45](https://github.com/ghiscoding/slickgrid-universal/commit/8af7f45eb19f0a00da2f3de7c729504be7d043eb)) - by @ghiscoding +* remove all Deprecated code ([#2302](https://github.com/ghiscoding/slickgrid-universal/issues/2302)) ([f42c46c](https://github.com/ghiscoding/slickgrid-universal/commit/f42c46cd1f05b5c72c62f552f124b5bfe776f8b0)) - by @ghiscoding + +### Code Refactoring + +* drop OperatorType enums and keep only type literal ([#2301](https://github.com/ghiscoding/slickgrid-universal/issues/2301)) ([5dd0807](https://github.com/ghiscoding/slickgrid-universal/commit/5dd08079460dc9af798ab29527997a6d4b31abdd)) - by @ghiscoding +* replacing multiple TypeScript `enum` with `type` to decrease build size ([#2300](https://github.com/ghiscoding/slickgrid-universal/issues/2300)) ([ea79395](https://github.com/ghiscoding/slickgrid-universal/commit/ea79395cf663b3abce8e43cf27ba6ffea7cfe113)) - by @ghiscoding + ## [9.13.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v9.13.0) (2026-01-30) ### Features diff --git a/demos/react/package.json b/demos/react/package.json index 36c35c1aec..606d096eb3 100644 --- a/demos/react/package.json +++ b/demos/react/package.json @@ -1,7 +1,7 @@ { "name": "slickgrid-react-demo", "private": true, - "version": "9.13.0", + "version": "10.0.0-beta.0", "description": "Slickgrid-React demo", "keywords": [ "react", @@ -31,14 +31,15 @@ "@slickgrid-universal/odata": "workspace:*", "@slickgrid-universal/pagination-component": "workspace:*", "@slickgrid-universal/pdf-export": "workspace:*", + "@slickgrid-universal/react-row-detail-plugin": "workspace:*", "@slickgrid-universal/row-detail-view-plugin": "workspace:*", "@slickgrid-universal/rxjs-observable": "workspace:*", "@slickgrid-universal/text-export": "workspace:*", "bootstrap": "catalog:", "dompurify": "catalog:", "i18next": "catalog:", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "react": "catalog:", + "react-dom": "catalog:", "react-i18next": "^16.5.4", "react-router": "^7.13.0", "slickgrid-react": "workspace:*" @@ -54,8 +55,8 @@ "@popperjs/core": "catalog:", "@types/fnando__sparkline": "catalog:", "@types/node": "catalog:", - "@types/react": "^19.2.10", - "@types/react-dom": "^19.2.3", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", "@types/sortablejs": "catalog:", "@vitejs/plugin-react": "catalog:", "cross-env": "catalog:", @@ -65,7 +66,7 @@ "rxjs": "catalog:", "sass": "catalog:", "typescript": "catalog:", - "vite": "catalog:vite7", + "vite": "catalog:", "vite-tsconfig-paths": "catalog:" } } diff --git a/demos/react/src/examples/slickgrid/App.tsx b/demos/react/src/examples/slickgrid/App.tsx index ac5a4381e0..b7fb17f03d 100644 --- a/demos/react/src/examples/slickgrid/App.tsx +++ b/demos/react/src/examples/slickgrid/App.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react'; import { Routes as BaseRoutes, Link, Navigate, Route, useLocation } from 'react-router'; import { NavBar } from '../../NavBar.js'; + import Example1 from './Example1.js'; import Example2 from './Example2.js'; import Example3 from './Example3.js'; @@ -50,6 +51,7 @@ import Example47 from './Example47.js'; import Example48 from './Example48.js'; import Example49 from './Example49.js'; import Example50 from './Example50.js'; +import Example51 from './Example51.js'; const routes: Array<{ path: string; route: string; component: any; title: string }> = [ { path: 'example1', route: '/example1', component: , title: '1- Basic Grid / 2 Grids' }, @@ -101,6 +103,7 @@ const routes: Array<{ path: string; route: string; component: any; title: string { path: 'example48', route: '/example48', component: , title: '48- Hybrid Selection Model' }, { path: 'example49', route: '/example49', component: , title: '49- Spreadsheet Drag-Fill' }, { path: 'example50', route: '/example50', component: , title: '50- Master/Detail Grids' }, + { path: 'example51', route: '/example51', component: , title: '51- Menus with Slots' }, ]; export default function Routes() { diff --git a/demos/react/src/examples/slickgrid/Example10.tsx b/demos/react/src/examples/slickgrid/Example10.tsx index 7b97c3d940..4a0f23deaf 100644 --- a/demos/react/src/examples/slickgrid/Example10.tsx +++ b/demos/react/src/examples/slickgrid/Example10.tsx @@ -150,7 +150,7 @@ const Example10: React.FC = () => { const gridOptions1: GridOption = { enableAutoResize: false, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, enableCheckboxSelector: true, enableFiltering: true, checkboxSelector: { @@ -162,7 +162,7 @@ const Example10: React.FC = () => { // selectableOverride: (row: number, dataContext: any, grid: SlickGrid) => (dataContext.id % 2 === 1) }, multiSelect: false, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: true, }, @@ -198,12 +198,12 @@ const Example10: React.FC = () => { hideInColumnTitleRow: true, applySelectOnAllPages: true, // when clicking "Select All", should we apply it to all pages (defaults to true) }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, gridHeight: 255, gridWidth: 800, enablePagination: true, @@ -303,7 +303,7 @@ const Example10: React.FC = () => { } function onGrid1SelectedRowsChanged(_e: Event, args: any) { - const grid = args && args.grid; + const grid = args?.grid; if (Array.isArray(args.rows)) { const selectedTitles = args.rows.map((idx: number) => { const item = grid.getDataItem(idx); diff --git a/demos/react/src/examples/slickgrid/Example11.tsx b/demos/react/src/examples/slickgrid/Example11.tsx index c3a2396f1f..364acaeb78 100644 --- a/demos/react/src/examples/slickgrid/Example11.tsx +++ b/demos/react/src/examples/slickgrid/Example11.tsx @@ -128,7 +128,7 @@ const Example11: React.FC = () => { editable: true, enableColumnPicker: true, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, }); } @@ -302,7 +302,7 @@ const Example11: React.FC = () => {
    • Adding an item, will always be showing as the 1st item in the grid because that is the best visual place to add it
    • Add/Update an item requires a valid Slickgrid Selection Model, you have 2 choices to deal with this:
      • -
      • You can enable "enableCheckboxSelector" or "enableRowSelection" to True
      • +
      • You can enable "enableCheckboxSelector" or "enableSelection" to True
    • Click on any of the buttons below to test this out
    • diff --git a/demos/react/src/examples/slickgrid/Example12.tsx b/demos/react/src/examples/slickgrid/Example12.tsx index 20dd7a181b..97cc6e66f5 100644 --- a/demos/react/src/examples/slickgrid/Example12.tsx +++ b/demos/react/src/examples/slickgrid/Example12.tsx @@ -4,7 +4,6 @@ import i18next from 'i18next'; import React, { useEffect, useRef, useState } from 'react'; import { withTranslation } from 'react-i18next'; import { - DelimiterType, Filters, Formatters, SlickgridReact, @@ -160,7 +159,7 @@ const Example12: React.FC = () => { hideInColumnTitleRow: true, }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, showCustomFooter: true, // display some metrics in the bottom custom footer customFooterOptions: { metricTexts: { @@ -253,7 +252,7 @@ const Example12: React.FC = () => { function exportToFile(type = 'csv') { textExportService.exportToFile({ - delimiter: type === 'csv' ? DelimiterType.comma : DelimiterType.tab, + delimiter: type === 'csv' ? ',' : '\t', filename: 'myExport', format: type === 'csv' ? 'csv' : 'txt', }); diff --git a/demos/react/src/examples/slickgrid/Example14.tsx b/demos/react/src/examples/slickgrid/Example14.tsx index 7f69f240f2..de8d8162e1 100644 --- a/demos/react/src/examples/slickgrid/Example14.tsx +++ b/demos/react/src/examples/slickgrid/Example14.tsx @@ -7,11 +7,12 @@ import './example14.scss'; // provide custom CSS/SASS styling const Example14: React.FC = () => { const [gridOptions1, setGridOptions1] = useState(undefined); const [gridOptions2, setGridOptions2] = useState(undefined); + const [isColspanSpreading, setIsColspanSpreading] = useState(false); const [columnDefinitions1, setColumnDefinitions1] = useState([]); const [columnDefinitions2, setColumnDefinitions2] = useState([]); const [dataset1, setDataset1] = useState([]); const [dataset2, setDataset2] = useState([]); - // const reactGridRef1 = useRef(null); + const reactGridRef1 = useRef(null); const reactGridRef2 = useRef(null); const [hideSubTitle, setHideSubTitle] = useState(false); @@ -22,9 +23,9 @@ const Example14: React.FC = () => { setDataset2(getData(500)); }, []); - // function reactGrid1Ready(reactGrid: SlickgridReactInstance) { - // reactGridRef1.current = reactGrid; - // } + function reactGrid1Ready(reactGrid: SlickgridReactInstance) { + reactGridRef1.current = reactGrid; + } function reactGrid2Ready(reactGrid: SlickgridReactInstance) { reactGridRef2.current = reactGrid; @@ -162,6 +163,14 @@ const Example14: React.FC = () => { }; } + function spreadColspan() { + const isSpreading = !isColspanSpreading; + setIsColspanSpreading(isSpreading); + reactGridRef1.current?.slickGrid?.setOptions({ spreadHiddenColspan: isSpreading }); + reactGridRef1.current?.slickGrid?.resetActiveCell(); + reactGridRef1.current?.slickGrid?.invalidate(); + } + return !gridOptions1 ? ( '' ) : ( @@ -200,8 +209,23 @@ const Example14: React.FC = () => {

      Grid 1 (with Header Grouping & Colspan) +

      - + + reactGrid1Ready($event.detail)} + />
      diff --git a/demos/react/src/examples/slickgrid/Example15.tsx b/demos/react/src/examples/slickgrid/Example15.tsx index d69b346c23..24f37ad400 100644 --- a/demos/react/src/examples/slickgrid/Example15.tsx +++ b/demos/react/src/examples/slickgrid/Example15.tsx @@ -51,8 +51,6 @@ const Example15: React.FC = () => { /** Clear the Grid State from Local Storage and reset the grid to it's original state */ function clearGridStateFromLocalStorage() { - // reactGridRef.current?.slickGrid.setColumns(reactGridRef.current?.gridService.getAllColumnDefinitions()); - // reactGridRef.current?.slickGrid.autosizeColumns(); reactGridRef.current?.gridService.resetGrid(getColumnDefinitions()); reactGridRef.current?.paginationService!.changeItemPerPage(DEFAULT_PAGE_SIZE); setTimeout(() => (localStorage[LOCAL_STORAGE_KEY] = null)); diff --git a/demos/react/src/examples/slickgrid/Example16.tsx b/demos/react/src/examples/slickgrid/Example16.tsx index bc13249039..05675ee4da 100644 --- a/demos/react/src/examples/slickgrid/Example16.tsx +++ b/demos/react/src/examples/slickgrid/Example16.tsx @@ -81,8 +81,8 @@ const Example16: React.FC = () => { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, }, - enableRowSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, diff --git a/demos/react/src/examples/slickgrid/Example18.tsx b/demos/react/src/examples/slickgrid/Example18.tsx index 0ec066a34c..2deea88e13 100644 --- a/demos/react/src/examples/slickgrid/Example18.tsx +++ b/demos/react/src/examples/slickgrid/Example18.tsx @@ -245,16 +245,17 @@ const Example18: React.FC = () => { initialGroupBy: ['duration'], }, darkMode, - enableTextExport: true, - enableExcelExport: true, excelExportOptions: { sanitizeDataExport: true }, textExportOptions: { sanitizeDataExport: true }, - enablePdfExport: true, pdfExportOptions: { repeatHeadersOnEachPage: true, // defaults to true documentTitle: 'Grouping Grid', }, externalResources: [excelExportService, pdfExportService, textExportService], + // -- NOTE: registered resources are auto-enabled + // enableTextExport: true, + // enablePdfExport: true, + // enableExcelExport: true, }; setColumnDefinitions(columnDefinitions); diff --git a/demos/react/src/examples/slickgrid/Example19.tsx b/demos/react/src/examples/slickgrid/Example19.tsx index bfeb2fc795..00bb3a839f 100644 --- a/demos/react/src/examples/slickgrid/Example19.tsx +++ b/demos/react/src/examples/slickgrid/Example19.tsx @@ -1,16 +1,7 @@ import { type EventPubSubService } from '@slickgrid-universal/event-pub-sub'; +import { ReactSlickRowDetailView } from '@slickgrid-universal/react-row-detail-plugin'; import React, { useEffect, useRef, useState } from 'react'; -import { - Editors, - ExtensionName, - Filters, - Formatters, - SlickgridReact, - SlickRowDetailView, - type Column, - type GridOption, - type SlickgridReactInstance, -} from 'slickgrid-react'; +import { Editors, Filters, Formatters, SlickgridReact, type Column, type GridOption, type SlickgridReactInstance } from 'slickgrid-react'; import { ExampleDetailPreload } from './Example-detail-preload.js'; import Example19DetailView from './Example19-detail-view.js'; @@ -41,7 +32,7 @@ const Example19: React.FC = () => { }, []); function rowDetailInstance() { - return reactGridRef.current?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView) as SlickRowDetailView; + return reactGridRef.current?.extensionService.getExtensionInstanceByName('rowDetailView') as ReactSlickRowDetailView; } const getColumnsDefinition = (): Column[] => { @@ -173,8 +164,8 @@ const Example19: React.FC = () => { darkMode, datasetIdPropertyName: 'rowId', preRegisterExternalExtensions: (pubSubService) => { - const rowDetail = new SlickRowDetailView(pubSubService as EventPubSubService); - return [{ name: ExtensionName.rowDetailView, instance: rowDetail }]; + const rowDetail = new ReactSlickRowDetailView(pubSubService as EventPubSubService); + return [{ name: 'rowDetailView', instance: rowDetail }]; }, rowDetailView: { process: (item) => simulateServerAsyncCall(item), @@ -192,7 +183,7 @@ const Example19: React.FC = () => { return true; }, }, - rowSelectionOptions: { + selectionOptions: { selectActiveRow: true, }, }; diff --git a/demos/react/src/examples/slickgrid/Example21.tsx b/demos/react/src/examples/slickgrid/Example21.tsx index 9e039ec98b..37f19d1744 100644 --- a/demos/react/src/examples/slickgrid/Example21.tsx +++ b/demos/react/src/examples/slickgrid/Example21.tsx @@ -1,12 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { - Formatters, - SlickgridReact, - type Column, - type GridOption, - type OperatorString, - type SlickgridReactInstance, -} from 'slickgrid-react'; +import { Formatters, SlickgridReact, type Column, type GridOption, type OperatorType, type SlickgridReactInstance } from 'slickgrid-react'; import './example21.scss'; const Example21: React.FC = () => { @@ -14,7 +7,7 @@ const Example21: React.FC = () => { const [dataset] = useState(getData()); const [gridOptions, setGridOptions] = useState(undefined); const reactGridRef = useRef(null); - const [operatorList] = useState(['=', '<', '<=', '>', '>=', '<>', 'StartsWith', 'EndsWith']); + const [operatorList] = useState(['=', '<', '<=', '>', '>=', '<>', 'StartsWith', 'EndsWith']); const [selectedOperator, setSelectedOperator] = useState(''); const [searchValue, setSearchValue] = useState(''); const [selectedColumn, setSelectedColumn] = useState(); @@ -107,7 +100,7 @@ const Example21: React.FC = () => { alwaysShowVerticalScroll: false, enableColumnPicker: true, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, }; setColumnDefinitions(columnDefinitions); @@ -163,7 +156,7 @@ const Example21: React.FC = () => { function updateFilter() { reactGridRef.current?.filterService.updateSingleFilter({ columnId: `${selectedColumn?.id ?? ''}`, - operator: selectedOperator as OperatorString, + operator: selectedOperator as OperatorType, searchTerms: [searchValue || ''], }); } diff --git a/demos/react/src/examples/slickgrid/Example23.tsx b/demos/react/src/examples/slickgrid/Example23.tsx index 86ed925a91..b1d3c6ce8f 100644 --- a/demos/react/src/examples/slickgrid/Example23.tsx +++ b/demos/react/src/examples/slickgrid/Example23.tsx @@ -7,7 +7,6 @@ import { withTranslation } from 'react-i18next'; import { Filters, Formatters, - OperatorType, SlickgridReact, type Column, type CurrentFilter, @@ -104,7 +103,7 @@ const Example23: React.FC = () => { filter: { model: Filters.sliderRange, maxValue: 100, // or you can use the options as well - operator: OperatorType.rangeInclusive, // defaults to inclusive + operator: 'RangeInclusive', // defaults to inclusive options: { hideSliderNumbers: false, // you can hide/show the slider numbers on both side min: 0, @@ -152,7 +151,7 @@ const Example23: React.FC = () => { filterable: true, filter: { model: Filters.input, - operator: OperatorType.rangeExclusive, // defaults to exclusive + operator: 'RangeExclusive', // defaults to exclusive }, }, { @@ -310,8 +309,8 @@ const Example23: React.FC = () => { switch (newPredefinedFilter) { case 'currentYearTasks': filters = [ - { columnId: 'finish', operator: OperatorType.rangeInclusive, searchTerms: [`${currentYear}-01-01`, `${currentYear}-12-31`] }, - { columnId: 'completed', operator: OperatorType.equal, searchTerms: [true] }, + { columnId: 'finish', operator: 'RangeInclusive', searchTerms: [`${currentYear}-01-01`, `${currentYear}-12-31`] }, + { columnId: 'completed', operator: '=', searchTerms: [true] }, ]; break; case 'nextYearTasks': diff --git a/demos/react/src/examples/slickgrid/Example24.tsx b/demos/react/src/examples/slickgrid/Example24.tsx index ec2e2c4095..839f631e9f 100644 --- a/demos/react/src/examples/slickgrid/Example24.tsx +++ b/demos/react/src/examples/slickgrid/Example24.tsx @@ -3,7 +3,6 @@ import i18next from 'i18next'; import React, { useEffect, useRef, useState } from 'react'; import { withTranslation } from 'react-i18next'; import { - ExtensionName, Filters, Formatters, SlickgridReact, @@ -85,11 +84,11 @@ const Example24: React.FC = () => { } function cellMenuInstance() { - return reactGridRef.current?.extensionService.getExtensionInstanceByName(ExtensionName.cellMenu); + return reactGridRef.current?.extensionService.getExtensionInstanceByName('cellMenu'); } function contextMenuInstance() { - return reactGridRef.current?.extensionService.getExtensionInstanceByName(ExtensionName.contextMenu); + return reactGridRef.current?.extensionService.getExtensionInstanceByName('contextMenu'); } /* Define grid Options and Columns */ @@ -348,7 +347,7 @@ const Example24: React.FC = () => { onCommand: (_e, args) => executeCommand(_e, args), onOptionSelected: (_e, args) => { // change "Completed" property with new option selected from the Cell Menu - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; if (dataContext && dataContext.hasOwnProperty('completed')) { dataContext.completed = args.item.option; reactGridRef.current?.gridService.updateItem(dataContext); @@ -429,7 +428,7 @@ const Example24: React.FC = () => { // optionally and conditionally define when the the menu is usable, // this should be used with a custom formatter to show/hide/disable the menu menuUsabilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return dataContext.id < 21; // say we want to display the menu only from Task 0 to 20 }, // which column to show the command list? when not defined it will be shown over all columns @@ -460,7 +459,7 @@ const Example24: React.FC = () => { }, // only show command to 'Help' when the task is Not Completed itemVisibilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return !dataContext.completed; }, }, @@ -526,7 +525,7 @@ const Example24: React.FC = () => { textCssClass: 'italic', // only enable this option when the task is Not Completed itemUsabilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return !dataContext.completed; }, // you can use the 'action' callback and/or subscribe to the 'onCallback' event, they both have the same arguments @@ -548,7 +547,7 @@ const Example24: React.FC = () => { disabled: true, // only shown when the task is Not Completed itemVisibilityOverride: (args) => { - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; return !dataContext.completed; }, }, @@ -579,7 +578,7 @@ const Example24: React.FC = () => { // subscribe to Context Menu onOptionSelected event (or use the action callback on each option) onOptionSelected: (_e, args) => { // change Priority - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; if (dataContext && dataContext.hasOwnProperty('priority')) { dataContext.priority = args.item.option; reactGridRef.current?.gridService.updateItem(dataContext); diff --git a/demos/react/src/examples/slickgrid/Example25.tsx b/demos/react/src/examples/slickgrid/Example25.tsx index a18b7fbbad..25d5893fb0 100644 --- a/demos/react/src/examples/slickgrid/Example25.tsx +++ b/demos/react/src/examples/slickgrid/Example25.tsx @@ -1,14 +1,6 @@ import { GraphqlService, type GraphqlResult, type GraphqlServiceApi } from '@slickgrid-universal/graphql'; import React, { useEffect, useState } from 'react'; -import { - Filters, - Formatters, - OperatorType, - SlickgridReact, - type Column, - type GridOption, - type MultipleSelectOption, -} from 'slickgrid-react'; +import { Filters, Formatters, SlickgridReact, type Column, type GridOption, type MultipleSelectOption } from 'slickgrid-react'; import './example25.scss'; // provide custom CSS/SASS styling const COUNTRIES_API = 'https://countries.trevorblades.com/'; @@ -78,7 +70,7 @@ const Example25: React.FC = () => { filter: { model: Filters.multipleSelect, collectionAsync: getLanguages(), - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', collectionOptions: { addBlankEntry: true, // the data is not at the root of the array, so we must tell the Select Filter where to pull the data @@ -106,7 +98,7 @@ const Example25: React.FC = () => { filter: { model: Filters.multipleSelect, collectionAsync: getLanguages(), - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', collectionOptions: { addBlankEntry: true, // the data is not at the root of the array, so we must tell the Select Filter where to pull the data @@ -268,7 +260,7 @@ const Example25: React.FC = () => { // function setFiltersDynamically() { // // we can Set Filters Dynamically (or different filters) afterward through the FilterService // reactGridRef.current?.filterService.updateFilters([ - // { columnId: 'countryName', searchTerms: ['G'], operator: OperatorType.startsWith }, + // { columnId: 'countryName', searchTerms: ['G'], operator: 'StartsWith' }, // ]); // } diff --git a/demos/react/src/examples/slickgrid/Example3.tsx b/demos/react/src/examples/slickgrid/Example3.tsx index 9d6570f883..9be182fa8e 100644 --- a/demos/react/src/examples/slickgrid/Example3.tsx +++ b/demos/react/src/examples/slickgrid/Example3.tsx @@ -4,7 +4,6 @@ import { Editors, Filters, Formatters, - OperatorType, SlickGlobalEditorLock, SlickgridReact, SortComparers, @@ -31,7 +30,7 @@ const NB_ITEMS = 100; // you can create custom validator to pass to an inline editor const myCustomTitleValidator: EditorValidator = (value: any) => { // you can get the Editor Args which can be helpful, e.g. we can get the Translate Service from it - // const grid = args && args.grid; + // const grid = args?.grid; // const gridOptions = grid.getOptions() as GridOption; // const i18n = gridOptions.i18n; @@ -197,7 +196,7 @@ const Example3: React.FC = () => { collectionFilterBy: { property: 'value', value: 0, - operator: OperatorType.notEqual, + operator: '!=', }, model: Editors.singleSelect, // validator: (value, args) => { @@ -418,7 +417,7 @@ const Example3: React.FC = () => { separatorBetweenTextLabels: ' ', }, model: Filters.multipleSelect, - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', }, }, ]); diff --git a/demos/react/src/examples/slickgrid/Example30.tsx b/demos/react/src/examples/slickgrid/Example30.tsx index c3fc545d9a..b246c7b786 100644 --- a/demos/react/src/examples/slickgrid/Example30.tsx +++ b/demos/react/src/examples/slickgrid/Example30.tsx @@ -482,7 +482,7 @@ const Example30: React.FC = () => { }, externalResources: [new ExcelExportService(), new SlickCustomTooltip(), compositeEditorInstanceRef.current!], enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -490,7 +490,7 @@ const Example30: React.FC = () => { showPreHeaderPanel: true, preHeaderPanelHeight: 28, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, multiSelect: false, checkboxSelector: { hideInFilterHeaderRow: false, diff --git a/demos/react/src/examples/slickgrid/Example31.tsx b/demos/react/src/examples/slickgrid/Example31.tsx index d8d6ff8e71..0d8de70ba0 100644 --- a/demos/react/src/examples/slickgrid/Example31.tsx +++ b/demos/react/src/examples/slickgrid/Example31.tsx @@ -5,7 +5,6 @@ import { Observable, of, type Subject } from 'rxjs'; import { Editors, Filters, - OperatorType, SlickgridReact, type Column, type GridOption, @@ -100,7 +99,7 @@ const Example31: React.FC = () => { enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enablePagination: true, // you could optionally disable the Pagination pagination: { pageSizes: [10, 20, 50, 100, 500], @@ -109,7 +108,7 @@ const Example31: React.FC = () => { }, presets: { // you can also type operator as string, e.g.: operator: 'EQ' - filters: [{ columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }], + filters: [{ columnId: 'gender', searchTerms: ['male'], operator: '=' }], sorters: [ // direction can be written as 'asc' (uppercase or lowercase) and/or use the SortDirection type { columnId: 'name', direction: 'asc' }, @@ -386,7 +385,7 @@ const Example31: React.FC = () => { function setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService reactGridRef.current?.filterService.updateFilters([ - // { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'gender', searchTerms: ['male'], operator: '=' }, { columnId: 'name', searchTerms: ['A'], operator: 'a*' }, ]); } diff --git a/demos/react/src/examples/slickgrid/Example32.tsx b/demos/react/src/examples/slickgrid/Example32.tsx index 074e920f97..220bfb1eba 100644 --- a/demos/react/src/examples/slickgrid/Example32.tsx +++ b/demos/react/src/examples/slickgrid/Example32.tsx @@ -491,13 +491,13 @@ const Example32: React.FC = () => { }, externalResources: [new ExcelExportService()], enableFiltering: true, - enableRowSelection: true, + enableSelection: true, enableCheckboxSelector: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -585,7 +585,7 @@ const Example32: React.FC = () => { // just for demo purposes, set it back to its original width const columns = reactGridRef.current?.slickGrid.getColumns() as Column[]; columns.forEach((col) => (col.width = col.originalWidth)); - reactGridRef.current?.slickGrid.setColumns(columns); + reactGridRef.current?.slickGrid.updateColumns(); reactGridRef.current?.slickGrid.autosizeColumns(); setIsUsingDefaultResize(true); } diff --git a/demos/react/src/examples/slickgrid/Example33.tsx b/demos/react/src/examples/slickgrid/Example33.tsx index 3f8acb60b4..fd4523d19e 100644 --- a/demos/react/src/examples/slickgrid/Example33.tsx +++ b/demos/react/src/examples/slickgrid/Example33.tsx @@ -2,10 +2,10 @@ import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; import React, { useEffect, useRef, useState } from 'react'; import { + createDomElement, Editors, Filters, Formatters, - OperatorType, SlickgridReact, type Column, type EditCommand, @@ -139,6 +139,31 @@ const Example33: React.FC = () => { // maxHeight: 30, }, }, + { + id: 'button', + name: 'Button Tooltip', + field: 'title', + width: 100, + minWidth: 100, + filterable: true, + excludeFromExport: true, + formatter: (_row: number, _cell: number, value: any) => { + const button = createDomElement('button', { + className: 'btn btn-outline-secondary btn-icon btn-sm', + title: 'This is the button tooltip', + }); + const icon = createDomElement('i', { className: 'mdi mdi-information', title: 'icon tooltip' }); + const text = createDomElement('span', { textContent: 'Hello Task' }); + button.appendChild(icon); + button.appendChild(text); + button.addEventListener('click', () => alert(`Clicked button for ${value}`)); + return button; + }, + // define tooltip options here OR for the entire grid via the grid options (cell tooltip options will have precedence over grid options) + customTooltip: { + useRegularTooltip: true, // note regular tooltip will try to find a "title" attribute in the cell formatter (it won't work without a cell formatter) + }, + }, { id: 'cost', name: 'Cost', @@ -318,7 +343,7 @@ const Example33: React.FC = () => { collectionOptions: { separatorBetweenTextLabels: ' ' }, options: { minHeight: 70 } as MultipleSelectOption, model: Filters.multipleSelect, - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', }, }, { @@ -401,19 +426,21 @@ const Example33: React.FC = () => { headerFormatter, headerRowFormatter, usabilityOverride: (args) => args.cell !== 0 && args?.column?.id !== 'action', // don't show on first/last columns + observeAllTooltips: true, // observe all elements with title/data-slick-tooltip attributes (not just SlickGrid elements) + observeTooltipContainer: 'body', // defaults to 'body', target a specific container (only works when observeAllTooltips is enabled) }, presets: { filters: [{ columnId: 'prerequisites', searchTerms: [1, 3, 5, 7, 9, 12, 15, 18, 21, 25, 28, 29, 30, 32, 34] }], }, - rowHeight: 33, + rowHeight: 38, enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, showCustomFooter: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, @@ -430,7 +457,7 @@ const Example33: React.FC = () => { onCommand: (e, args) => executeCommand(e, args), onOptionSelected: (_e, args) => { // change "Completed" property with new option selected from the Cell Menu - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; if (dataContext && dataContext.hasOwnProperty('completed')) { dataContext.completed = args.item.option; reactGridRef.current?.gridService.updateItem(dataContext); @@ -464,7 +491,7 @@ const Example33: React.FC = () => { id: i, title: 'Task ' + i, duration: Math.round(Math.random() * 100), - description: `This is a sample task description.\nIt can be multiline\r\rAnother line...`, + description: i > 500 ? null : `This is a sample task description.\nIt can be multiline\r\rAnother line...`, percentComplete: Math.floor(Math.random() * (100 - 5 + 1) + 5), start: new Date(randomYear, randomMonth, randomDay), finish: randomFinish < new Date() ? '' : randomFinish, // make sure the random date is earlier than today @@ -574,6 +601,20 @@ const Example33: React.FC = () => { reactGridRef.current?.resizerService.resizeGrid(0); } + function setFiltersDynamically(operator: string) { + const operatorType = operator === '=' ? '=' : '!='; + reactGridRef.current?.filterService.updateFilters( + [ + { + columnId: 'desc', + operator: operatorType, + searchTerms: [''], + }, + ], + true + ); + } + return !gridOptions ? ( '' ) : ( @@ -625,6 +666,24 @@ const Example33: React.FC = () => { value={serverWaitDelay} onInput={($event) => handleServerDelayInputChange($event)} /> + +
    Lazy loading collection... diff --git a/demos/react/src/examples/slickgrid/Example38.tsx b/demos/react/src/examples/slickgrid/Example38.tsx index 9cd4aecdb0..635ddb2a07 100644 --- a/demos/react/src/examples/slickgrid/Example38.tsx +++ b/demos/react/src/examples/slickgrid/Example38.tsx @@ -97,7 +97,7 @@ const Example38: React.FC = () => { enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enableGrouping: true, presets: { // NOTE: pagination preset is NOT supported with infinite scroll diff --git a/demos/react/src/examples/slickgrid/Example4.tsx b/demos/react/src/examples/slickgrid/Example4.tsx index 876eee3977..1f72903327 100644 --- a/demos/react/src/examples/slickgrid/Example4.tsx +++ b/demos/react/src/examples/slickgrid/Example4.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from 'react'; import { Filters, Formatters, - OperatorType, SlickgridReact, type Column, type GridOption, @@ -78,12 +77,12 @@ const Example4: React.FC = () => { collectionFilterBy: [ { property: 'value', - operator: OperatorType.notEqual, + operator: '!=', value: 360, }, { property: 'value', - operator: OperatorType.notEqual, + operator: '!=', value: 365, }, ], diff --git a/demos/react/src/examples/slickgrid/Example41.tsx b/demos/react/src/examples/slickgrid/Example41.tsx index 8fea240bfc..62847530ea 100644 --- a/demos/react/src/examples/slickgrid/Example41.tsx +++ b/demos/react/src/examples/slickgrid/Example41.tsx @@ -53,11 +53,12 @@ const Example41: React.FC = () => { gridWidth: 800, rowHeight: 33, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, enableRowMoveManager: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, + selectionType: 'row', }, rowMoveManager: { columnIndexPosition: 0, diff --git a/demos/react/src/examples/slickgrid/Example42.tsx b/demos/react/src/examples/slickgrid/Example42.tsx index 557157c929..7a13751cce 100644 --- a/demos/react/src/examples/slickgrid/Example42.tsx +++ b/demos/react/src/examples/slickgrid/Example42.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { Filters, Formatters, - OperatorType, SlickgridReact, type Column, type GridOption, @@ -68,7 +67,7 @@ const Example42: React.FC = () => { filter: { model: Filters.sliderRange, maxValue: 100, // or you can use the options as well - operator: OperatorType.rangeInclusive, // defaults to inclusive + operator: 'RangeInclusive', // defaults to inclusive options: { hideSliderNumbers: false, // you can hide/show the slider numbers on both side min: 0, @@ -114,7 +113,7 @@ const Example42: React.FC = () => { filterable: true, filter: { model: Filters.input, - operator: OperatorType.rangeExclusive, // defaults to exclusive + operator: 'RangeExclusive', // defaults to exclusive }, }, { diff --git a/demos/react/src/examples/slickgrid/Example43.tsx b/demos/react/src/examples/slickgrid/Example43.tsx index 6ade36e3df..b34f643539 100644 --- a/demos/react/src/examples/slickgrid/Example43.tsx +++ b/demos/react/src/examples/slickgrid/Example43.tsx @@ -418,7 +418,8 @@ export default function Example43() { } } - // update column definitions + // 1. update column definitions via grid.setColumns() + // this will shift colspan/rowspan to the left or right accordingly const cols: Column[] = reactGrid?.slickGrid.getColumns() || []; if (newShowEmployeeId) { cols.unshift({ id: 'employeeID', name: 'Employee ID', field: 'employeeID', width: 100 }); @@ -427,6 +428,22 @@ export default function Example43() { } reactGrid?.slickGrid.setColumns(cols || []); + // --- OR --- + // 2. OR update via "hidden" column flag & increase/decrease column index accordingly in the metadata + // this approach will keep colspan/rowspan "as-is" but will hide the EmployeeID column + /* + const colDirIdx = newShowEmployeeId ? -1 : 1; + for (const row of Object.keys(this.metadata)) { + newMetadata[row] = { columns: {} }; + for (const col of Object.keys((this.metadata as any)[row].columns)) { + newMetadata[row].columns[Number(col) + colDirIdx] = (this.metadata as any)[row].columns[col]; + } + } + reactGrid?.slickGrid?.setOptions({ frozenColumn: newShowEmployeeId ? 0 : 1 }); + reactGrid?.slickGrid?.updateColumnById('employeeID', { hidden: !newShowEmployeeId }); + reactGrid?.slickGrid?.updateColumns(); + */ + // update & remap rowspans metadataRef.current = newMetadata; reactGrid?.slickGrid.remapAllColumnsRowSpan(); diff --git a/demos/react/src/examples/slickgrid/Example45.tsx b/demos/react/src/examples/slickgrid/Example45.tsx index cba90b4e52..7943cf4fde 100644 --- a/demos/react/src/examples/slickgrid/Example45.tsx +++ b/demos/react/src/examples/slickgrid/Example45.tsx @@ -1,14 +1,8 @@ import { faker } from '@faker-js/faker'; import { type EventPubSubService } from '@slickgrid-universal/event-pub-sub'; +import { ReactSlickRowDetailView } from '@slickgrid-universal/react-row-detail-plugin'; import React, { useEffect, useRef, useState } from 'react'; -import { - ExtensionName, - SlickgridReact, - SlickRowDetailView, - type Column, - type GridOption, - type SlickgridReactInstance, -} from 'slickgrid-react'; +import { SlickgridReact, type Column, type GridOption, type SlickgridReactInstance } from 'slickgrid-react'; import Example45DetailView, { type Distributor, type OrderData } from './Example45-detail-view.js'; import { Example45Preload } from './Example45-preload.js'; @@ -47,7 +41,7 @@ const Example45: React.FC = () => { }, [isUsingInnerGridStatePresets]); function rowDetailInstance() { - return reactGridRef.current?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView) as SlickRowDetailView; + return reactGridRef.current?.extensionService.getExtensionInstanceByName('rowDetailView') as ReactSlickRowDetailView; } function getColumnDefinitions(): Column[] { @@ -164,8 +158,8 @@ const Example45: React.FC = () => { rowHeight: 33, darkMode, preRegisterExternalExtensions: (pubSubService) => { - const rowDetail = new SlickRowDetailView(pubSubService as EventPubSubService); - return [{ name: ExtensionName.rowDetailView, instance: rowDetail }]; + const rowDetail = new ReactSlickRowDetailView(pubSubService as EventPubSubService); + return [{ name: 'rowDetailView', instance: rowDetail }]; }, rowDetailView: { process: (item) => simulateServerAsyncCall(item), diff --git a/demos/react/src/examples/slickgrid/Example46.tsx b/demos/react/src/examples/slickgrid/Example46.tsx index 3244806113..a03cabe60c 100644 --- a/demos/react/src/examples/slickgrid/Example46.tsx +++ b/demos/react/src/examples/slickgrid/Example46.tsx @@ -135,7 +135,7 @@ const Example46: React.FC = () => { sanitizeDataExport: true, }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, multiSelect: false, checkboxSelector: { // columnIndexPosition: 1, diff --git a/demos/react/src/examples/slickgrid/Example47.tsx b/demos/react/src/examples/slickgrid/Example47.tsx index d1989e5093..41ecbf76b5 100644 --- a/demos/react/src/examples/slickgrid/Example47.tsx +++ b/demos/react/src/examples/slickgrid/Example47.tsx @@ -1,4 +1,5 @@ import { type EventPubSubService } from '@slickgrid-universal/event-pub-sub'; +import { ReactSlickRowDetailView } from '@slickgrid-universal/react-row-detail-plugin'; import React, { useEffect, useRef, useState } from 'react'; import { Aggregators, @@ -8,7 +9,6 @@ import { Formatters, GroupTotalFormatters, SlickgridReact, - SlickRowDetailView, SortComparers, SortDirectionNumber, type Column, @@ -60,7 +60,7 @@ const Example47: React.FC = () => { } function rowDetailInstance() { - return reactGridRef.current?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView) as SlickRowDetailView; + return reactGridRef.current?.extensionService.getExtensionInstanceByName(ExtensionName.rowDetailView) as ReactSlickRowDetailView; } const getColumnsDefinition = (): Column[] => { @@ -200,8 +200,8 @@ const Example47: React.FC = () => { rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" darkMode, preRegisterExternalExtensions: (pubSubService) => { - const rowDetail = new SlickRowDetailView(pubSubService as EventPubSubService); - return [{ name: ExtensionName.rowDetailView, instance: rowDetail }]; + const rowDetail = new ReactSlickRowDetailView(pubSubService as EventPubSubService); + return [{ name: 'rowDetailView', instance: rowDetail }]; }, rowDetailView: { process: (item) => simulateServerAsyncCall(item), @@ -211,7 +211,7 @@ const Example47: React.FC = () => { preloadComponent: ExampleDetailPreload, viewComponent: Example47DetailView, }, - rowSelectionOptions: { + selectionOptions: { selectActiveRow: true, }, }; diff --git a/demos/react/src/examples/slickgrid/Example48.tsx b/demos/react/src/examples/slickgrid/Example48.tsx index c9997e6ee8..f1b82d6265 100644 --- a/demos/react/src/examples/slickgrid/Example48.tsx +++ b/demos/react/src/examples/slickgrid/Example48.tsx @@ -91,9 +91,10 @@ const Example48: React.FC = () => { externalResources: [new ExcelExportService()], // enable new hybrid selection model (rows & cells) - enableHybridSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { rowSelectColumnIds: ['id'], + selectionType: 'mixed', }, // when using the ExcelCopyBuffer, you can see what the selection range is @@ -111,8 +112,8 @@ const Example48: React.FC = () => { ...gridOptions1, // you can also enable checkbox selection & row selection, make sure to use `rowSelectColumnIds: ['id', '_checkbox_selector']` enableCheckboxSelector: true, - enableRowSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, @@ -211,8 +212,7 @@ const Example48: React.FC = () => { {hideSubTitle ? null : (
    SlickHybridSelectionModel This Selection Model is an hybrid approach that uses a combination of the row or cell - selections depending on certain conditions. Use enableHybridSelection grid option to enable the new Hybrid Selection - Model. + selections depending on certain conditions.
    • 1. clicking on the first column (id) will use RowSelectionModel because of our configuration of diff --git a/demos/react/src/examples/slickgrid/Example49.tsx b/demos/react/src/examples/slickgrid/Example49.tsx index 09c5debe6a..2a011ddf22 100644 --- a/demos/react/src/examples/slickgrid/Example49.tsx +++ b/demos/react/src/examples/slickgrid/Example49.tsx @@ -75,10 +75,11 @@ const Example49: React.FC = () => { editorNavigateOnArrows: true, // enable editor navigation using arrow keys // enable new hybrid selection model (rows & cells) - enableHybridSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { selectActiveRow: true, rowSelectColumnIds: ['selector'], + selectionType: 'mixed', }, // when using the ExcelCopyBuffer, you can see what the selection range is @@ -180,7 +181,9 @@ const Example49: React.FC = () => {
      Spreadsheet with drag-fill, hybrid selection model. Type a few values in the grid and then select those cells and use the bottom right drag handle spread the selection and auto-fill the values to other cells. Use onDragReplaceCells event to - customize the drag-fill behavior. Use enableHybridSelection grid option to enable the new Hybrid Selection Model. + customize the drag-fill behavior. Use{' '} + { enableSelection: true, selectionOptions: { selectionType: 'mixed' }} + grid option to enable the new Hybrid Selection Model.

      diff --git a/demos/react/src/examples/slickgrid/Example5.tsx b/demos/react/src/examples/slickgrid/Example5.tsx index b29bcf9adf..78ae5bbf0e 100644 --- a/demos/react/src/examples/slickgrid/Example5.tsx +++ b/demos/react/src/examples/slickgrid/Example5.tsx @@ -3,7 +3,6 @@ import { GridOdataService, type OdataOption, type OdataServiceApi } from '@slick import { useEffect, useRef, useState } from 'react'; import { Filters, - OperatorType, SlickgridReact, type Column, type GridOption, @@ -65,13 +64,13 @@ const Example5: React.FC = () => { hideInColumnTitleRow: true, }, compoundOperatorAltTexts: { - // where '=' is any of the `OperatorString` type shown above + // where '=' is any of the `OperatorType` type shown above text: { Custom: { operatorAlt: '%%', descAlt: 'SQL Like' } }, }, enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enablePagination: true, // you could optionally disable the Pagination pagination: { pageSizes: [10, 20, 50, 100, 500, 50000], @@ -80,7 +79,7 @@ const Example5: React.FC = () => { }, presets: { // you can also type operator as string, e.g.: operator: 'EQ' - filters: [{ columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }], + filters: [{ columnId: 'gender', searchTerms: ['male'], operator: '=' }], sorters: [ // direction can be written as 'asc' (uppercase or lowercase) and/or use the SortDirection type { columnId: 'name', direction: 'asc' }, @@ -94,7 +93,7 @@ const Example5: React.FC = () => { enableSelect: isSelectEnabled, enableExpand: isExpandEnabled, filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { let matchesSearch = searchValues[0].replace(/\*/g, '.*'); matchesSearch = matchesSearch.slice(0, 1) + CARET_HTML_ESCAPED + matchesSearch.slice(1); matchesSearch = matchesSearch.slice(0, -1) + "$'"; @@ -431,7 +430,7 @@ const Example5: React.FC = () => { function setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService reactGridRef.current?.filterService.updateFilters([ - // { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'gender', searchTerms: ['male'], operator: '=' }, { columnId: 'name', searchTerms: ['A'], operator: 'a*' }, ]); } diff --git a/demos/react/src/examples/slickgrid/Example50.tsx b/demos/react/src/examples/slickgrid/Example50.tsx index a86cbdb2e6..d316e3b060 100644 --- a/demos/react/src/examples/slickgrid/Example50.tsx +++ b/demos/react/src/examples/slickgrid/Example50.tsx @@ -54,8 +54,8 @@ const Example50: React.FC = () => { gridHeight: 225, gridWidth: 800, rowHeight: 33, - enableHybridSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { selectionType: 'row', }, }; diff --git a/demos/react/src/examples/slickgrid/Example51.tsx b/demos/react/src/examples/slickgrid/Example51.tsx new file mode 100644 index 0000000000..28c4504a7e --- /dev/null +++ b/demos/react/src/examples/slickgrid/Example51.tsx @@ -0,0 +1,694 @@ +import { format as tempoFormat } from '@formkit/tempo'; +import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; +import React, { useEffect, useRef, useState } from 'react'; +import { + Aggregators, + createDomElement, + Filters, + Formatters, + SlickgridReact, + SortComparers, + SortDirectionNumber, + type Column, + type GridOption, + type Grouping, + type MenuCommandItem, + type SlickgridReactInstance, +} from 'slickgrid-react'; +import './example51.scss'; // provide custom CSS/SASS styling + +const NB_ITEMS = 2000; + +interface ReportItem { + id: number; + title: string; + duration: number; + cost: number; + percentComplete: number; + start: Date; + finish: Date; + action?: string; +} + +const Example51: React.FC = () => { + const [columnDefinitions, setColumnDefinitions] = useState[]>([]); + const [gridOptions, setGridOptions] = useState(); + const [dataset] = useState(loadData(NB_ITEMS)); + const [hideSubTitle, setHideSubTitle] = useState(false); + + const reactGridRef = useRef(null); + + useEffect(() => { + defineGrid(); + }, []); + + function reactGridReady(reactGrid: SlickgridReactInstance) { + reactGridRef.current = reactGrid; + } + + /* Define grid Options and Columns */ + function defineGrid() { + const columnDefinitions: Column[] = [ + { + id: 'title', + name: 'Title', + field: 'title', + sortable: true, + filterable: true, + minWidth: 100, + // Demo: Header Menu with Slot - complete custom HTML with keyboard shortcuts + header: { + menu: { + commandItems: [ + { + command: 'sort-asc', + title: 'Sort Ascending', + positionOrder: 50, + // Slot renderer replaces entire menu item content (can be HTML string or native DOM elements) + slotRenderer: (cmdItem) => ` + + `, + }, + { + command: 'sort-desc', + title: 'Sort Descending', + positionOrder: 51, + // Slot renderer using native DOM elements + slotRenderer: () => { + const menuItemElm = createDomElement('div', { className: 'menu-item' }); + const iconElm = createDomElement('i', { className: 'mdi mdi-sort-descending menu-item-icon' }); + const menuItemLabelElm = createDomElement('span', { className: 'menu-item-label', textContent: 'Sort Descending' }); + const kbdElm = createDomElement('kbd', { className: 'key-hint', textContent: 'Alt+โ†“' }); + menuItemElm.appendChild(iconElm); + menuItemElm.appendChild(menuItemLabelElm); + menuItemElm.appendChild(kbdElm); + return menuItemElm; + }, + }, + ], + }, + }, + }, + { + id: 'duration', + name: 'Duration', + field: 'duration', + sortable: true, + filterable: true, + minWidth: 100, + // Demo: Header Menu with Slot - showing badge and status dot + header: { + menu: { + commandItems: [ + { + command: 'column-resize-by-content', + title: 'Resize by Content', + positionOrder: 47, + // Slot renderer with badge + slotRenderer: () => ` + + `, + }, + { divider: true, command: '', positionOrder: 48 }, + { + command: 'sort-asc', + title: 'Sort Ascending', + iconCssClass: 'mdi mdi-sort-ascending', + positionOrder: 50, + }, + { + command: 'sort-desc', + title: 'Sort Descending', + iconCssClass: 'mdi mdi-sort-descending', + positionOrder: 51, + }, + { divider: true, command: '', positionOrder: 52 }, + { + command: 'clear-sort', + title: 'Remove Sort', + positionOrder: 58, + // Slot renderer with status indicator + slotRenderer: () => ` + + `, + }, + ], + }, + }, + }, + { + id: 'start', + name: 'Start', + field: 'start', + sortable: true, + formatter: Formatters.dateIso, + filterable: true, + filter: { model: Filters.compoundDate }, + minWidth: 100, + }, + { + id: 'finish', + name: 'Finish', + field: 'finish', + sortable: true, + formatter: Formatters.dateIso, + filterable: true, + filter: { model: Filters.dateRange }, + minWidth: 100, + }, + { + id: 'cost', + name: 'Cost', + field: 'cost', + width: 90, + sortable: true, + filterable: true, + formatter: Formatters.dollar, + // Demo: Header Menu with Slot - showing slotRenderer with callback (item, args) + header: { + menu: { + commandItems: [ + { + command: 'custom-action', + title: 'Advanced Export', + // Demo: Native HTMLElement with event listeners using slotRenderer (full DOM control) + slotRenderer: (cmdItem) => { + // you can use `createDomElement()` from Slickgrid for easier DOM element creation + const containerDiv = createDomElement('div', { className: 'menu-item' }); + const iconDiv = createDomElement('div', { className: 'advanced-export-icon', textContent: '๐Ÿ“Š' }); + const textSpan = createDomElement('span', { textContent: cmdItem.title || '', style: { flex: '1' } }); + const kbdElm = createDomElement('kbd', { className: 'key-hint', textContent: 'Ctrl+E' }); + containerDiv.appendChild(iconDiv); + containerDiv.appendChild(textSpan); + containerDiv.appendChild(kbdElm); + + // Add native event listeners for hover effects + containerDiv.addEventListener('mouseover', () => { + iconDiv.style.transform = 'scale(1.15)'; + iconDiv.style.background = 'linear-gradient(135deg, #d8dcef 0%, #ffffff 100%)'; + containerDiv.parentElement!.style.backgroundColor = '#854685'; + containerDiv.parentElement!.title = `๐Ÿ“ˆ Export timestamp: ${tempoFormat(new Date(), 'YYYY-MM-DD hh:mm:ss a')}`; + containerDiv.style.color = 'white'; + containerDiv.querySelector('.key-hint')!.style.color = 'black'; + }); + containerDiv.addEventListener('mouseout', () => { + iconDiv.style.transform = 'scale(1)'; + iconDiv.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; + containerDiv.parentElement!.style.backgroundColor = 'white'; + containerDiv.style.color = 'black'; + document.querySelector('.export-timestamp')?.remove(); + }); + + return containerDiv; + }, + action: () => { + alert('Custom export action triggered!'); + }, + }, + { divider: true, command: '' }, + { + command: 'filter-column', + title: 'Filter Column', + // Slot renderer with status indicator and beta badge + slotRenderer: () => ` + + `, + }, + ], + }, + }, + }, + { + id: 'percentComplete', + name: '% Complete', + field: 'percentComplete', + sortable: true, + filterable: true, + type: 'number', + filter: { model: Filters.slider, operator: '>=' }, + // Demo: Header Menu with Slot - showing interactive element (checkbox) + header: { + menu: { + commandItems: [ + { + command: 'recalc', + title: 'Recalculate', + iconCssClass: 'mdi mdi-refresh', + slotRenderer: () => ` + + `, + }, + ], + }, + }, + }, + { + id: 'action', + name: 'Action', + field: 'action', + width: 70, + minWidth: 70, + maxWidth: 70, + cssClass: 'justify-center flex', + formatter: () => + `
      `, + excludeFromExport: true, + // Demo: Cell Menu with slot examples (demonstrating defaultMenuItemRenderer at menu level) + cellMenu: { + hideCloseButton: false, + commandTitle: 'Cell Actions', + // Demo: Menu-level default renderer that applies to all items unless overridden + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + commandItems: [ + { + command: 'copy-cell', + title: 'Copy Cell Value', + iconCssClass: 'mdi mdi-content-copy', + action: (_e, args) => { + console.log('Copy cell value:', args.dataContext[args.column.field]); + alert(`Copied: ${args.dataContext[args.column.field]}`); + }, + }, + 'divider', + { + command: 'export-row', + title: 'Export Row', + iconCssClass: 'mdi mdi-download', + action: (_e, args) => { + console.log('Export row:', args.dataContext); + alert(`Export row #${args.dataContext.id}`); + }, + }, + { + command: 'export', + title: 'Export', + iconCssClass: 'mdi mdi-download', + commandItems: [ + { + command: 'export-excel', + title: 'Export as Excel', + iconCssClass: 'mdi mdi-file-excel-outline text-success', + action: (_e, args) => { + alert(`Export row #${args.dataContext.id} to Excel`); + }, + }, + { + command: 'export-csv', + title: 'Export as CSV', + iconCssClass: 'mdi mdi-file-document-outline', + action: (_e, args) => { + alert(`Export row #${args.dataContext.id} to CSV`); + }, + }, + { + command: 'export-pdf', + title: 'Export as PDF', + iconCssClass: 'mdi mdi-file-pdf-outline text-red', + action: (_e, args) => { + alert(`Export row #${args.dataContext.id} to PDF`); + }, + }, + ], + }, + { divider: true, command: '' }, + { + command: 'edit-row', + title: 'Edit Row', + // Individual slotRenderer overrides the defaultMenuItemRenderer + slotRenderer: (_item, args) => ` + + `, + action: (_e, args) => { + console.log('Edit row:', args.dataContext); + alert(`Edit row #${args.dataContext.id}`); + }, + }, + { + command: 'delete-row', + title: 'Delete Row', + iconCssClass: 'mdi mdi-delete text-danger', + action: (_event, args) => { + const dataContext = args.dataContext; + if (confirm(`Do you really want to delete row (${args.row! + 1}) with "${dataContext.title}"`)) { + reactGridRef.current?.gridService.deleteItemById(dataContext.id); + } + }, + }, + ], + }, + }, + ]; + + const gridOptions: GridOption = { + autoResize: { + container: '#demo-container', + }, + enableAutoResize: true, + enableCellNavigation: true, + enableFiltering: true, + enableSorting: true, + enableGrouping: true, + + // Header Menu with slots (already configured in columns above) + enableHeaderMenu: true, + headerMenu: { + // hideCommands: ['column-resize-by-content', 'clear-sort'], + + // Demo: Menu-level default renderer for all header menu items + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + }, + + // Cell Menu with slots (configured in the Action column above) + enableCellMenu: true, + + // Context Menu with slot examples + enableContextMenu: true, + contextMenu: { + // hideCommands: ['clear-grouping', 'copy'], + + // build your command items list + // spread built-in commands and optionally filter/sort them however you want + commandListBuilder: (builtInItems) => { + // commandItems.sort((a, b) => (a === 'divider' || b === 'divider' ? 0 : a.title! > b.title! ? -1 : 1)); + return [ + // filter commands if you want + // ...builtInItems.filter((x) => x !== 'divider' && x.command !== 'copy' && x.command !== 'clear-grouping'), + { + command: 'edit-cell', + title: 'Edit Cell', + // Demo: Individual slotRenderer overrides the menu's defaultMenuItemRenderer + slotRenderer: (cmdItem) => { + // you can use `createDomElement()` from Slickgrid for easier DOM element creation + const containerDiv = createDomElement('div', { className: 'menu-item' }); + const iconDiv = createDomElement('div', { className: 'edit-cell-icon', textContent: 'โœŽ' }); + const textSpan = createDomElement('span', { textContent: cmdItem.title || '', style: { flex: '1' } }); + const kbdElm = createDomElement('kbd', { className: 'edit-cell', textContent: 'F2' }); + containerDiv.appendChild(iconDiv); + containerDiv.appendChild(textSpan); + containerDiv.appendChild(kbdElm); + + // Native event listeners for interactive effects + containerDiv.addEventListener('mouseover', () => { + iconDiv.style.transform = 'rotate(15deg) scale(1.1)'; + iconDiv.style.boxShadow = '0 2px 8px rgba(0,200,83,0.4)'; + }); + containerDiv.addEventListener('mouseout', () => { + iconDiv.style.transform = 'rotate(0deg) scale(1)'; + iconDiv.style.boxShadow = 'none'; + }); + + return containerDiv; + }, + action: () => alert('Edit cell'), + }, + ...builtInItems, + { divider: true, command: '' }, + { + command: 'export', + title: 'Export', + iconCssClass: 'mdi mdi-download', + commandItems: [ + { + command: 'export-excel', + title: 'Export as Excel', + iconCssClass: 'mdi mdi-file-excel-outline text-success', + action: () => alert('Export to Excel'), + }, + { + command: 'export-csv', + title: 'Export as CSV', + iconCssClass: 'mdi mdi-file-document-outline', + action: () => alert('Export to CSV'), + }, + { + command: 'export-pdf', + title: 'Export as PDF', + iconCssClass: 'mdi mdi-file-pdf-outline text-danger', + action: () => alert('Export to PDF'), + }, + ], + }, + { divider: true, command: '' }, + { + command: 'delete-row', + title: 'Delete Row', + iconCssClass: 'mdi mdi-delete text-danger', + action: () => alert('Delete row'), + }, + ] as Array; + }, + // Demo: Menu-level default renderer for context menu items + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + }, + + // Grid Menu with slot examples (demonstrating defaultMenuItemRenderer at menu level) + enableGridMenu: true, + gridMenu: { + // hideCommands: ['toggle-preheader', 'toggle-filter'], + + // Demo: Menu-level default renderer that applies to all items (can be overridden per item with slotRenderer) + defaultMenuItemRenderer: (cmdItem) => { + return ` + + `; + }, + commandListBuilder: (builtInItems) => { + return [ + ...builtInItems, + { divider: true, command: '' }, + { + command: 'export-excel', + title: 'Export to Excel', + iconCssClass: 'mdi mdi-file-excel-outline', + action: () => alert('Export to Excel'), + }, + { + command: 'export-csv', + title: 'Export to CSV', + iconCssClass: 'mdi mdi-download', + // Individual slotRenderer overrides the defaultMenuItemRenderer for this item + slotRenderer: (cmdItem) => ` + + `, + action: () => alert('Export to CSV'), + }, + { + command: 'refresh-data', + title: 'Refresh Data', + iconCssClass: 'mdi mdi-refresh', + // Demo: slotRenderer with keyboard shortcut + slotRenderer: (cmdItem) => { + // you can use `createDomElement()` from Slickgrid for easier DOM element creation + const menuItemElm = createDomElement('div', { className: 'menu-item' }); + const iconElm = createDomElement('i', { className: `${cmdItem.iconCssClass} menu-item-icon` }); + const menuItemLabelElm = createDomElement('span', { className: 'menu-item-label', textContent: cmdItem.title || '' }); + const kbdElm = createDomElement('kbd', { className: 'key-hint', textContent: 'F5' }); + menuItemElm.appendChild(iconElm); + menuItemElm.appendChild(menuItemLabelElm); + menuItemElm.appendChild(kbdElm); + return menuItemElm; + }, + action: () => alert('Refresh data'), + }, + ] as Array; + }, + }, + + // tooltip plugin + externalResources: [new SlickCustomTooltip()], + customTooltip: { + observeAllTooltips: true, + }, + }; + + setColumnDefinitions(columnDefinitions); + setGridOptions(gridOptions); + } + + function clearGrouping() { + reactGridRef.current?.dataView?.setGrouping([]); + } + + function collapseAllGroups() { + reactGridRef.current?.dataView?.collapseAllGroups(); + } + + function expandAllGroups() { + reactGridRef.current?.dataView?.expandAllGroups(); + } + + function groupByDuration() { + // you need to manually add the sort icon(s) in UI + reactGridRef.current?.slickGrid?.setSortColumns([{ columnId: 'duration', sortAsc: true }]); + reactGridRef.current?.dataView?.setGrouping({ + getter: 'duration', + formatter: (g) => `Duration: ${g.value} (${g.count} items)`, + comparer: (a, b) => SortComparers.numeric(a.value, b.value, SortDirectionNumber.asc), + aggregators: [new Aggregators.Avg('percentComplete'), new Aggregators.Sum('cost')], + aggregateCollapsed: false, + lazyTotalsCalculation: true, + } as Grouping); + reactGridRef.current?.slickGrid?.invalidate(); // invalidate all rows and re-render + } + + function loadData(count: number): ReportItem[] { + const tmpData: ReportItem[] = []; + for (let i = 0; i < count; i++) { + const randomDuration = Math.round(Math.random() * 100); + const randomYear = 2000 + Math.floor(Math.random() * 10); + const randomMonth = Math.floor(Math.random() * 11); + const randomDay = Math.floor(Math.random() * 29); + const randomPercent = Math.round(Math.random() * 100); + + tmpData[i] = { + id: i, + title: 'Task ' + i, + duration: randomDuration, + cost: Math.round(Math.random() * 10000) / 100, + percentComplete: randomPercent, + start: new Date(randomYear, randomMonth, randomDay), + finish: new Date(randomYear, randomMonth + 1, randomDay), + }; + } + return tmpData; + } + + function toggleSubTitle() { + const newHideSubTitle = !hideSubTitle; + setHideSubTitle(newHideSubTitle); + const action = newHideSubTitle ? 'add' : 'remove'; + document.querySelector('.subtitle')?.classList[action]('hidden'); + reactGridRef.current?.resizerService.resizeGrid(0); + } + + return !gridOptions ? ( + '' + ) : ( +
      +

      + Example 51: Menus with Slots + + see  + + code + + + +

      + +
      +
      + + Menu Slots Demo with Custom Renderer +
      +

      + Click on the menu buttons to see the new single slot functionality working across all menu types (Header Menu, + Cell Menu, Context Menu, Grid Menu): +

      +

      + + Note: The demo focuses on the custom rendering capability via slotRenderer and + defaultMenuItemRenderer, which work across all menu plugins (SlickHeaderMenu, SlickCellMenu, SlickContextMenu, + SlickGridMenu). Also note that the keyboard shortcuts displayed in the menus (e.g., Alt+โ†‘, F5) are for + demo purposes only and do not actually trigger any actions. + +

      +
      + +
      +
      + + + + +
      +
      + +
      + reactGridReady($event.detail)} + /> +
      +
      + ); +}; + +export default Example51; diff --git a/demos/react/src/examples/slickgrid/Example6.tsx b/demos/react/src/examples/slickgrid/Example6.tsx index a90768193e..689eb5298e 100644 --- a/demos/react/src/examples/slickgrid/Example6.tsx +++ b/demos/react/src/examples/slickgrid/Example6.tsx @@ -11,9 +11,7 @@ import { withTranslation } from 'react-i18next'; import { Filters, Formatters, - OperatorType, SlickgridReact, - SortDirection, type Column, type CursorPageInfo, type GridOption, @@ -222,14 +220,14 @@ const Example6: React.FC = () => { { columnId: 'finish', width: 130 }, ], filters: [ - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, - { columnId: 'name', searchTerms: ['Joh*oe'], operator: OperatorType.startsWithEndsWith }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, + { columnId: 'name', searchTerms: ['Joh*oe'], operator: 'StartsWithEndsWith' }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }, - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ], sorters: [ { columnId: 'name', direction: 'asc' }, - { columnId: 'company', direction: SortDirection.DESC }, + { columnId: 'company', direction: 'DESC' }, ], pagination: { pageNumber: isWithCursorRef.current ? 1 : 2, pageSize: 20 }, }, @@ -240,7 +238,7 @@ const Example6: React.FC = () => { addLocaleIntoQuery: true, extraQueryArguments: [{ field: 'userId', value: 123 }], filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { return { field: fieldName, operator: 'Like', value: searchValues[0] }; } return; @@ -339,11 +337,11 @@ const Example6: React.FC = () => { const presetHighestDay = `${currentYear}-02-15`; reactGridRef.current?.filterService.updateFilters([ - { columnId: 'gender', searchTerms: ['female'], operator: OperatorType.equal }, - { columnId: 'name', searchTerms: ['Jane'], operator: OperatorType.startsWith }, + { columnId: 'gender', searchTerms: ['female'], operator: '=' }, + { columnId: 'name', searchTerms: ['Jane'], operator: 'StartsWith' }, { columnId: 'company', searchTerms: ['acme'], operator: 'IN' }, - { columnId: 'billingAddressZip', searchTerms: ['11'], operator: OperatorType.greaterThanOrEqual }, - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'billingAddressZip', searchTerms: ['11'], operator: '>=' }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ]); } @@ -360,14 +358,14 @@ const Example6: React.FC = () => { const presetHighestDay = `${currentYear}-02-15`; reactGridRef.current?.filterService.updateFilters([ - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, - { columnId: 'name', searchTerms: ['Joh*oe'], operator: OperatorType.startsWithEndsWith }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, + { columnId: 'name', searchTerms: ['Joh*oe'], operator: 'StartsWithEndsWith' }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }, - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ]); reactGridRef.current?.sortService.updateSorting([ { columnId: 'name', direction: 'asc' }, - { columnId: 'company', direction: SortDirection.DESC }, + { columnId: 'company', direction: 'DESC' }, ]); setTimeout(() => { reactGridRef.current?.paginationService?.changeItemPerPage(20); diff --git a/demos/react/src/examples/slickgrid/Example9.tsx b/demos/react/src/examples/slickgrid/Example9.tsx index 2f0141cb23..04fe1413fc 100644 --- a/demos/react/src/examples/slickgrid/Example9.tsx +++ b/demos/react/src/examples/slickgrid/Example9.tsx @@ -2,7 +2,6 @@ import i18next from 'i18next'; import React, { useEffect, useRef, useState } from 'react'; import { withTranslation } from 'react-i18next'; import { - ExtensionName, Filters, Formatters, SlickgridReact, @@ -296,7 +295,7 @@ const Example9: React.FC = () => { function toggleGridMenu(e: MouseEvent) { if (reactGridRef.current?.extensionService) { - const gridMenuInstance = reactGridRef.current.extensionService.getExtensionInstanceByName(ExtensionName.gridMenu); + const gridMenuInstance = reactGridRef.current.extensionService.getExtensionInstanceByName('gridMenu'); gridMenuInstance.showGridMenu(e, { dropSide: 'right' }); } } diff --git a/demos/react/src/examples/slickgrid/custom-inputFilter.tsx b/demos/react/src/examples/slickgrid/custom-inputFilter.tsx index 58229792fc..db1f53d002 100644 --- a/demos/react/src/examples/slickgrid/custom-inputFilter.tsx +++ b/demos/react/src/examples/slickgrid/custom-inputFilter.tsx @@ -1,13 +1,12 @@ import { emptyElement, - OperatorType, type Column, type ColumnFilter, type Filter, type FilterArguments, type FilterCallback, type GridOption, - type OperatorString, + type OperatorType, type SearchTerm, type SlickGrid, } from 'slickgrid-react'; @@ -20,7 +19,7 @@ export class CustomInputFilter implements Filter { searchTerms: SearchTerm[] = []; columnDef!: Column; callback!: FilterCallback; - operator: OperatorType | OperatorString = OperatorType.equal; + operator: OperatorType = 'EQ'; /** Getter for the Filter Operator */ get columnFilter(): ColumnFilter { diff --git a/demos/react/src/examples/slickgrid/example51.scss b/demos/react/src/examples/slickgrid/example51.scss new file mode 100644 index 0000000000..539ad2ecd2 --- /dev/null +++ b/demos/react/src/examples/slickgrid/example51.scss @@ -0,0 +1,144 @@ +body { + --slick-menu-item-height: 30px; + --slick-menu-line-height: 30px; + --slick-column-picker-item-height: 28px; + --slick-column-picker-line-height: 28px; + --slick-menu-item-border-radius: 4px; + --slick-menu-item-hover-border: 1px solid #148dff; + --slick-column-picker-item-hover-color: #fff; + --slick-column-picker-item-border-radius: 4px; + --slick-column-picker-item-hover-border: 1px solid #148dff; + --slick-menu-item-hover-color: #fff; + --slick-tooltip-background-color: #4c4c4c; + --slick-tooltip-color: #fff; + --slick-tooltip-font-size: 14px; + .slick-cell-menu, + .slick-context-menu, + .slick-grid-menu, + .slick-header-menu { + .slick-menu-item:hover:not(.slick-menu-item-disabled) { + color: #0a34b5; + } + } + .slick-menu-footer { + padding: 4px 6px; + border-top: 1px solid #c0c0c0; + } +} + +kbd { + background-color: #eee; + color: #202020; +} +.key-hint { + background: #eee; + border: 1px solid #ccc; + border-radius: 2px; + padding: 2px 4px; + font-size: 10px; + margin-left: 10px; + white-space: nowrap; + display: inline-flex; + align-items: center; + height: 20px; + + &.beta, + &.danger, + &.warn { + color: white; + font-size: 8px; + font-weight: bold; + } + &.beta { + background: #4444ff; + border: 1px solid #5454ff; + } + + &.danger { + background: #ff4444; + border: 1px solid #fb5a5a; + } + + &.warn { + background: #ff9800; + border: 1px solid #fba321; + } +} + +.edit-cell { + // background: #eee; + border: 1px solid #ccc; + border-radius: 2px; + padding: 2px 4px; + font-size: 10px; + margin-left: 10px; + display: inline-flex; + align-items: center; + height: 18px; +} + +.export-timestamp { + background-color: #4c4c4c; + color: #fff; + padding: 8px; + border-radius: 4px; + position: absolute; + z-index: 999999; +} + +.advanced-export-icon, +.edit-cell-icon, +.recalc-icon { + width: 20px; + height: 20px; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 4px; + transition: transform 0.2s; + color: white; + font-size: 10px; +} +.advanced-export-icon { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} +.edit-cell-icon { + background: linear-gradient(135deg, #00c853 0%, #64dd17 100%); +} +.recalc-icon { + background: linear-gradient(135deg, #c800a3 0%, #a31189 100%); +} + +.round-tag { + width: 6px; + height: 6px; + border-radius: 50%; + display: inline-block; + background: #44ff44; + box-shadow: 0 0 4px #44ff44; + margin-left: 10px; +} + +.menu-item { + display: flex; + align-items: center; + flex: 1; + justify-content: space-between; + + .menu-item-label.warn { + flex: 1; + color: #f09000; + } +} +.menu-item-icon { + margin-right: 4px; + font-size: 18px; + &.warn { + color: #ff9800; + } +} + +.menu-item-label { + flex: 1; +} diff --git a/demos/react/src/examples/slickgrid/utilities.ts b/demos/react/src/examples/slickgrid/utilities.ts index b4c1bff6ef..ae78294c55 100644 --- a/demos/react/src/examples/slickgrid/utilities.ts +++ b/demos/react/src/examples/slickgrid/utilities.ts @@ -30,13 +30,6 @@ export function showToast(msg: string, type: 'danger' | 'info' | 'warning', time }, time); return; } - - // @deprecated, remove fallback in next major release - // otherwise, fallback (when popover is not supported): keep the div visible as regular HTML and remove after timeout. - divContainer.style.left = '50%'; - divContainer.style.top = '20px'; - divContainer.style.transform = 'translateX(-50%)'; - setTimeout(() => divContainer.remove(), time); } export function zeroPadding(input: string | number) { diff --git a/demos/react/test/cypress/e2e/example10.cy.ts b/demos/react/test/cypress/e2e/example10.cy.ts index 0dd651b40f..22544dc7ab 100644 --- a/demos/react/test/cypress/e2e/example10.cy.ts +++ b/demos/react/test/cypress/e2e/example10.cy.ts @@ -46,11 +46,8 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should have 2 rows (Task 12,Task 13) selected in 2nd grid after typing in a search filter', () => { cy.get('#grid2').find('.filter-title').type('Task 1'); - cy.get('#grid2').find('.slick-row').should('not.have.length', 0); - cy.get('[data-test=grid2-selections]').should('contain', ''); - cy.get('#grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); @@ -125,7 +122,7 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { describe('Pagination', () => { it('should Clear all Filters on 2nd Grid', () => { - cy.get('#grid2').find('button.slick-grid-menu-button').click(); + cy.get('#grid2').find('button.slick-grid-menu-button').trigger('click').click(); cy.get(`.slick-grid-menu:visible`).find('.slick-menu-item').first().find('span').contains('Clear all Filters').click(); }); @@ -141,112 +138,72 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { .then((pageNumber) => expect(pageNumber).to.eq('2')); cy.get('@grid1').find('[data-test=page-count]').contains('99'); - cy.get('@grid1').find('[data-test=item-from]').contains('6'); - cy.get('@grid1').find('[data-test=item-to]').contains('10'); - cy.get('@grid1').find('[data-test=total-items]').contains('495'); // 2nd Grid cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('5'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); }); it('should change Page Number in Grid1 and expect the Pagination to have correct values', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('[data-test=page-number-input]').clear().type('52').type('{enter}'); - cy.get('@grid1').find('[data-test=page-count]').contains('99'); - cy.get('@grid1').find('[data-test=item-from]').contains('256'); - cy.get('@grid1').find('[data-test=item-to]').contains('260'); - cy.get('@grid1').find('[data-test=total-items]').contains('495'); }); it('should change Page Number and Page Size in Grid2 and expect the Pagination to have correct values', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('34').type('{enter}'); - cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('166'); - cy.get('@grid2').find('[data-test=item-to]').contains('170'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); - cy.get('@grid2').find('#items-per-page-label').select('75'); - cy.get('@grid2').find('[data-test=page-count]').contains('7'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('75'); }); it('should go back to Grid1 and expect the same value before changing Pagination of Grid2', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('[data-test=page-count]').contains('99'); - cy.get('@grid1').find('[data-test=item-from]').contains('256'); - cy.get('@grid1').find('[data-test=item-to]').contains('260'); - cy.get('@grid1').find('[data-test=total-items]').contains('495'); }); it('should display page 0 of 0 with 0 items when applied filter returning an empty dataset', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('.filter-title').type('000'); - cy.get('.slick-empty-data-warning:visible').contains('No data to display.'); - cy.get('@grid1').find('[data-test=page-count]').contains('0'); - cy.get('@grid1').find('[data-test=item-from]').should('not.be.visible'); - cy.get('@grid1').find('[data-test=item-to]').should('not.be.visible'); - cy.get('@grid1').find('[data-test=total-items]').contains('0'); }); it('should erase part of the filter to have "00" and expect 4 items in total with 1 page', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('.filter-title').type('{backspace}'); - cy.get('.slick-empty-data-warning').contains('No data to display.').should('not.be.visible'); - cy.get('@grid1').find('[data-test=page-count]').contains('1'); - cy.get('@grid1').find('[data-test=item-from]').contains('1'); - cy.get('@grid1').find('[data-test=item-to]').contains('4'); - cy.get('@grid1').find('[data-test=total-items]').contains('4'); }); it('should also expect Grid2 to be unchanged (after changing Pagination in Grid1 in previous tests)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('[data-test=page-count]').contains('7'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('75'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); }); @@ -265,35 +222,24 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should go to Page 3 of 2nd Grid and have 2 rows selected in that Page and also have 4 rows selected in the entire grid (Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('#items-per-page-label').select('5'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('3').type('{enter}'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should go to last Page of 2nd Grid and have 1 rows selected in that Page and also have 4 rows selected in the entire grid (Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.icon-seek-end').click(); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 1); }); it(`should go to first Page of 2nd Grid and select another row (Task 1) in that Page, wich will now be (Task1,Task3) and now have 5 rows selected in the entire grid (Task 1,Task 3,Task 12,Task 13,Task 522)`, () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.icon-seek-first').click().wait(10); - cy.get('@grid2').find('.slick-row:nth(1) .slick-cell:nth(0) input[type=checkbox]').click({ force: true }); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); cy.window().then((win) => { @@ -313,23 +259,16 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should go back to Page 3 of 2nd Grid and have 2 rows selected in that Page and also retain 5 selected rows in the entire grid (Task 1,Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('#items-per-page-label').select('5'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('3').type('{enter}'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should go to last Page of 2nd Grid and still have 1 row selected in that Page and also retain 5 selected rows in the entire grid (Task 1,Task 3,Task 12,Task 13,Task 522)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.icon-seek-end').click(); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 1); }); }); @@ -366,27 +305,18 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should go to Page 61 of Grid1 and expect to find "Task 300" still be selected', () => { cy.get('#slickGridContainer-grid1').as('grid1'); - cy.get('@grid1').find('[data-test=page-number-input]').clear().type('61').type('{enter}'); - cy.get('[data-test=grid1-selections]').contains('Task 300'); - cy.get('.slick-cell.l0.r0.slick-cell-checkboxsel.selected').should('exist'); - cy.get('[data-test=grid1-selections]').contains('Task 300'); }); it('should go to a different page for next test to confirm that it will then go to page 1', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('[data-test=page-number-input]').clear().type('22').type('{enter}'); - cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('106'); - cy.get('@grid2').find('[data-test=item-to]').contains('110'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); }); @@ -453,35 +383,26 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should have 5 rows (Task 1,Task 3,Task 12,Task 13,Task 522) selected in the entire 2nd grid BUT only 2 shown in the DOM in the top portion of the grid (because SlickGrid uses virtual rendering)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should scroll to the bottom of 2nd Grid and still have 5 rows (Task 1,Task 3,Task 12,Task 13,Task 522) selected and find 2 row selected because we now have 2 rows that got rendered (first and last)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(10); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should have 2 rows (Task 3,Task 13) selected in 2nd grid after typing in a search filter (3)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.filter-title').type('3'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(10); - cy.get('@grid2').find('.slick-row').should('not.have.length', 0); cy.wait(50); cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 13'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); cy.window().then((win) => { @@ -514,9 +435,7 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { describe('Re-enable Pagination', () => { it('should re-enable the Pagination and expect to see it show it again below the grid at Page 1', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('[data-test=toggle-pagination-grid2]').click(); - cy.get('#slickGridContainer-grid2 .slick-pagination').should('exist'); cy.get('@grid2') @@ -525,13 +444,9 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { .then((pageNumber) => expect(pageNumber).to.eq('1')); cy.get('@grid2').find('[data-test=page-number-input]').click(); - cy.get('@grid2').find('[data-test=page-count]').contains('105'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('5'); - cy.get('@grid2').find('[data-test=total-items]').contains('525'); cy.window().then((win) => { @@ -549,13 +464,9 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should have 2 rows (Task 3,Task 13) selected in 2nd grid after typing in a search filter (3)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); - cy.get('@grid2').find('.filter-title').type('3'); - cy.get('@grid2').find('.slick-row').should('not.have.length', 0); - cy.get('[data-test=grid2-selections]').should('contain', 'Task 3,Task 13'); - cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); cy.window().then((win) => { @@ -583,11 +494,8 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { .then((pageNumber) => expect(pageNumber).to.eq('1')); cy.get('@grid2').find('[data-test=page-count]').contains('3'); - cy.get('@grid2').find('[data-test=item-from]').contains('1'); - cy.get('@grid2').find('[data-test=item-to]').contains('5'); - cy.get('@grid2').find('[data-test=total-items]').contains('179'); }); }); diff --git a/demos/react/test/cypress/e2e/example14.cy.ts b/demos/react/test/cypress/e2e/example14.cy.ts index 402545254b..91f7db34b6 100644 --- a/demos/react/test/cypress/e2e/example14.cy.ts +++ b/demos/react/test/cypress/e2e/example14.cy.ts @@ -21,16 +21,16 @@ describe('Example 14 - Column Span & Header Grouping', () => { }); it('should have a frozen grid on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 2); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 3); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]`).children().should('have.length', 4); + cy.get('#grid2').find('[data-row=0]').should('have.length', 2); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]> .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]> .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]> .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]> .slick-cell:nth(0)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]> .slick-cell:nth(1)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(1)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid again', () => { @@ -48,14 +48,14 @@ describe('Example 14 - Column Span & Header Grouping', () => { it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { cy.get('[data-test="remove-frozen-column-button"]').click(); - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 1); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 7); + cy.get('#grid2').find('[data-row=0]').should('have.length', 1); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid once again', () => { @@ -73,16 +73,16 @@ describe('Example 14 - Column Span & Header Grouping', () => { it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { cy.contains('Set 3 Frozen Columns').click({ force: true }); - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 2); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 3); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0]`).children().should('have.length', 4); + cy.get('#grid2').find('[data-row=0]').should('have.length', 2); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0] > .slick-cell:nth(0)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-right > [data-row=0] > .slick-cell:nth(1)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(1)').should('contain', '01/05/2009'); }); it('should have still exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -102,14 +102,14 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.contains('Unfreeze Columns/Rows').click({ force: true }); - cy.get('#grid2').find(`[data-row=0]`).should('have.length', 1); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0]`).children().should('have.length', 7); + cy.get('#grid2').find('[data-row=0]').should('have.length', 1); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)`).should('contain', '0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)`).should('contain', '01/01/2009'); - cy.get('#grid2').find(`.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)`).should('contain', '01/05/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); + cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); }); it('should reapply 3 frozen columns on 2nd grid', () => { @@ -209,7 +209,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { }); describe('Colspan checks on 1st grid', () => { - it('should hide Finish column and still expect "5 days" to spread accross 3 column', () => { + it('should hide Finish column and expect "5 days" spread to drop from 3 to 2 columns (1x hidden column)', () => { cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').should('contain', 'Task 1'); cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r5').should('contain', 'Task 2'); cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r3').should('contain', '5 days'); @@ -232,11 +232,64 @@ describe('Example 14 - Column Span & Header Grouping', () => { .should('contain', 'Hide Column') .click(); + // goto right + cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').click(); + cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0.active').should('contain', 'Task 1').type('{rightArrow}'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r3.active').should('contain', '5 days').type('{rightArrow}'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l4.r4.active').contains(/\d+$/).type('{rightArrow}'); + cy.get('#grid1') + .find('[data-row=1] .slick-cell.l5.r5') + .contains(/(true|false)+$/); + + // goto left + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active') + .contains(/(true|false)+$/) + .type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').contains(/\d+$/).type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3.active').should('contain', '5 days').type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0.active').should('contain', 'Task 1'); + }); + + it('should reset Column Picker, click on Spread Hidden Coolumn button then hide Finish column and still expect "5 days" to spread accross 3 column', () => { + cy.get('#grid1').find('.slick-header-column:nth(1)').trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children('li:nth-child(4)') + .children('label') + .should('contain', 'Period - Finish') + .click(); + + cy.get('.slick-column-picker .close').click(); + + cy.get('[data-test="spread-colspan-button"]').click(); cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').should('contain', 'Task 1'); - cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r4').should('contain', 'Task 2'); + cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r5').should('contain', 'Task 2'); cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r3').should('contain', '5 days'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l4.r4').contains(/\d+$/); + cy.get('#grid1') + .find('[data-row=1] .slick-cell.l5.r5') + .contains(/(true|false)+$/); + + cy.get('#grid1') + .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + + cy.get('.slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-content') + .should('contain', 'Hide Column') + .click(); + + cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').should('contain', 'Task 1'); + cy.get('#grid1').find('[data-row=2] .slick-cell.l0.r5').should('contain', 'Task 2'); + cy.get('#grid1').find('[data-row=1] .slick-cell.l1.r4').should('contain', '5 days'); cy.get('#grid1') - .find('[data-row=1] .slick-cell.l4.r4') + .find('[data-row=1] .slick-cell.l5.r5') .contains(/(true|false)+$/); }); @@ -292,15 +345,13 @@ describe('Example 14 - Column Span & Header Grouping', () => { describe('First Grid - Key Navigation', () => { it('should start at Task 1 and expect "Duration" to have colspan of 3 and show "% Complete" and "Effort Driven"', () => { cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1').click().type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days').type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4').should('have.class', 'active').contains(/\d+$/).type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5') - .should('have.class', 'active') - .contains(/(true|false)+$/); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3.active').should('contain', '5 days').type('{rightArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').contains(/\d+$/).type('{rightArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active').contains(/(true|false)+$/); cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active').type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4').should('have.class', 'active').contains(/\d+$/).type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days'); + cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').contains(/\d+$/).type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3.active').should('contain', '5 days'); cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1'); }); @@ -312,16 +363,17 @@ describe('Example 14 - Column Span & Header Grouping', () => { .children('label') .should('contain', 'Period - Finish') .click(); + cy.get('.slick-column-picker .close').click(); cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1').click().type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days').type('{rightArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4') + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r4.active').should('contain', '5 days').type('{rightArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5') .should('have.class', 'active') .contains(/(true|false)+$/); - cy.get('#grid1 [data-row=1] > .slick-cell.l4.r4.active').type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').should('have.class', 'active').should('contain', '5 days').type('{leftArrow}'); - cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('have.class', 'active'); + cy.get('#grid1 [data-row=1] > .slick-cell.l5.r5.active').type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r4.active').should('contain', '5 days').type('{leftArrow}'); + cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0.active'); cy.get('#grid1 [data-row=1] > .slick-cell.l0.r0').should('contain', 'Task 1'); }); }); diff --git a/demos/react/test/cypress/e2e/example16.cy.ts b/demos/react/test/cypress/e2e/example16.cy.ts index a1a2073bb5..78dabd7ae7 100644 --- a/demos/react/test/cypress/e2e/example16.cy.ts +++ b/demos/react/test/cypress/e2e/example16.cy.ts @@ -408,7 +408,7 @@ describe('Example 16 - Row Move & Checkbox Selector Selector Plugins', () => { }); it('should add Edit/Delete columns and expect 2 new columns added at the beginning of the grid', () => { - const newExpectedColumns = ['', '', ...fullTitles]; + const newExpectedColumns = ['', '', '', '', 'Title', '% Complete', 'Start', 'Finish', 'Duration', 'Completed']; cy.get('[data-test="add-crud-columns-btn"]').click(); cy.get('#grid16') diff --git a/demos/react/test/cypress/e2e/example18.cy.ts b/demos/react/test/cypress/e2e/example18.cy.ts index 55b380a1a1..b9807b0bc8 100644 --- a/demos/react/test/cypress/e2e/example18.cy.ts +++ b/demos/react/test/cypress/e2e/example18.cy.ts @@ -122,11 +122,10 @@ describe('Example 18 - Draggable Grouping & Aggregators', () => { it('should expand all rows with "Expand All" from context menu and expect all the Groups to be expanded and the Toogle All icon to be collapsed', () => { cy.get('#grid18').find('.slick-row .slick-cell:nth(1)').rightclick({ force: true }); - cy.get('.slick-context-menu .slick-menu-command-list') - .find('.slick-menu-item') - .find('.slick-menu-content') - .contains('Expand all Groups') - .click(); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Export in CSV format'); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Export to Excel'); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Export to PDF'); + cy.get('.slick-context-menu .slick-menu-command-list').find('.slick-menu-content').contains('Expand all Groups').click(); cy.get('#grid18').find('.slick-group-toggle.collapsed').should('have.length', 0); diff --git a/demos/react/test/cypress/e2e/example32.cy.ts b/demos/react/test/cypress/e2e/example32.cy.ts index c2c8d5f769..1c8eb7b69e 100644 --- a/demos/react/test/cypress/e2e/example32.cy.ts +++ b/demos/react/test/cypress/e2e/example32.cy.ts @@ -181,7 +181,12 @@ describe('Example 32 - Columns Resize by Content', () => { const yesterdayDate = format(addDay(new Date(), -1), 'YYYY-MM-DD'); const todayDate = format(new Date(), 'YYYY-MM-DD'); - cy.get(`[data-vc-date=${yesterdayDate}]`).should('have.attr', 'data-vc-date-disabled'); + // Check if yesterday's date element exists (may not be visible when 1st day of the month is a Sunday, e.g. 2026-02-01) + cy.get(`[data-vc-date=${yesterdayDate}]`).then(($el) => { + if ($el.length > 0) { + expect($el).to.have.attr('data-vc-date-disabled'); + } + }); cy.get(`[data-vc-date=${todayDate}]`).should('not.have.attr', 'data-vc-date-disabled'); // make grid readonly again @@ -379,16 +384,21 @@ describe('Example 32 - Columns Resize by Content', () => { }); it('should be able to edit "Duration" when "autoEditByKeypress" is enabled and by clicking once on second row and expect next row to become editable', () => { - cy.get('[data-row="2"] .slick-cell.l2.r2').contains(/[0-9]* days/); - cy.get('[data-row="2"] .slick-cell.l2.r2').click(); - cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); - - cy.get('[data-row="2"] .slick-cell.l2.r2').type('123'); - cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 1); - cy.get('[data-row="2"] .slick-cell.l2.r2').type('{enter}'); - cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); - - cy.get('[data-row="2"] .slick-cell.l2.r2').should('contain', '123 days'); + // Check if yesterday's date element exists (may not be visible when we run the test on the 1st day of the month and it is a Sunday, e.g. 2026-02-01) + cy.get('[data-row="2"] .slick-cell.l2.r2').then(($el) => { + if ($el.length > 0) { + cy.wrap($el).contains(/[0-9]* days/); + cy.wrap($el).click(); + cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); + + cy.get('[data-row="2"] .slick-cell.l2.r2').type('123'); + cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 1); + cy.get('[data-row="2"] .slick-cell.l2.r2').type('{enter}'); + cy.get('[data-row="2"] .slick-cell.l2.r2.active.editable').should('have.length', 0); + + cy.get('[data-row="2"] .slick-cell.l2.r2').should('contain', '123 days'); + } + }); }); it('should click on "Auto-Edit by keyboard OFF" button', () => { diff --git a/demos/react/test/cypress/e2e/example33.cy.ts b/demos/react/test/cypress/e2e/example33.cy.ts index 3db64520b9..998a33b382 100644 --- a/demos/react/test/cypress/e2e/example33.cy.ts +++ b/demos/react/test/cypress/e2e/example33.cy.ts @@ -5,6 +5,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { 'Duration', 'Description', 'Description 2', + 'Button Tooltip', 'Cost', '% Complete', 'Start', @@ -13,7 +14,6 @@ describe('Example 33 - Regular & Custom Tooltips', () => { 'Prerequisites', 'Action', ]; - const GRID_ROW_HEIGHT = 33; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example33`); @@ -32,7 +32,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over 1st row checkbox column and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0)`).as('checkbox0-cell'); + cy.get('[data-row="0"] > .slick-cell:nth(0)').as('checkbox0-cell'); cy.get('@checkbox0-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); @@ -40,7 +40,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over Task 2 cell and expect async tooltip to show', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(1)`).as('task1-cell'); + cy.get('[data-row="0"] > .slick-cell:nth(1)').as('task1-cell'); cy.get('@task1-cell').should('contain', 'Task 2'); cy.get('@task1-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').contains('loading...'); @@ -65,7 +65,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over Task 6 cell and expect async tooltip to show', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(1)`).as('task6-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(1)').as('task6-cell'); cy.get('@task6-cell').should('contain', 'Task 6'); cy.get('@task6-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').contains('loading...'); @@ -87,7 +87,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { it('should mouse over Task 6 cell on "Start" column and expect a delayed tooltip opening via async process', () => { cy.get('.slick-custom-tooltip').should('not.exist'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(7)`).as('start6-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(8)').as('start6-cell'); cy.get('@start6-cell').contains(/\d{4}-\d{2}-\d{2}$/); // use regexp to make sure it's a number cy.get('@start6-cell').trigger('mouseover'); @@ -111,7 +111,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over 6th row Description and expect full cell content to show in a tooltip because cell has ellipsis and is too long for the cell itself', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(3)`).as('desc6-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(3)').as('desc6-cell'); cy.get('@desc6-cell').should('contain', 'This is a sample task description.'); cy.get('@desc6-cell').trigger('mouseover'); @@ -126,7 +126,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over 6th row Description 2 and expect regular tooltip title + concatenated full cell content when using "useRegularTooltipFromFormatterOnly: true"', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(4)`).as('desc2-5-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(4)').as('desc2-5-cell'); cy.get('@desc2-5-cell').should('contain', 'This is a sample task description.'); cy.get('@desc2-5-cell').trigger('mouseover'); @@ -139,8 +139,24 @@ describe('Example 33 - Regular & Custom Tooltips', () => { cy.get('@desc2-5-cell').trigger('mouseout'); }); + it('should mouse over Button Tooltip column and verify button and icon tooltips show correctly', () => { + cy.get('[data-row="2"] > .slick-cell:nth(5)').as('button-cell'); + + // Hover over the button element and expect its tooltip + cy.get('@button-cell').find('button').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip').should('contain', 'This is the button tooltip'); + cy.get('@button-cell').find('button').trigger('mouseout'); + + // Hover over the icon inside the button and expect its tooltip + cy.get('@button-cell').find('i.mdi').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip').should('contain', 'icon tooltip'); + cy.get('@button-cell').find('i.mdi').trigger('mouseout'); + }); + it('should mouse over 2nd row Duration and expect a custom tooltip shown with 4 label/value pairs displayed', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(2)`).as('duration2-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(2)').as('duration2-cell'); cy.get('@duration2-cell').contains(/\d+\sday[s]?$/); cy.get('@duration2-cell').trigger('mouseover'); @@ -163,7 +179,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over % Complete cell of Task 6 and expect regular tooltip to show with content "x %" where x is a number', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(6)`).as('percentage-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(7)').as('percentage-cell'); cy.get('@percentage-cell').find('.percent-complete-bar').should('exist'); cy.get('@percentage-cell').trigger('mouseover'); @@ -174,7 +190,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over Prerequisite cell of Task 6 and expect regular tooltip to show with content "Task 6, Task 5"', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(10)`).as('prereq-cell'); + cy.get('[data-row="2"] > .slick-cell:nth(11)').as('prereq-cell'); cy.get('@prereq-cell').should('contain', 'Task 6, Task 5'); cy.get('@prereq-cell').trigger('mouseover'); @@ -206,7 +222,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over header-row (filter) Finish column and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(8)`).as('finish-filter'); + cy.get(`.slick-headerrow-columns .slick-headerrow-column:nth(9)`).as('finish-filter'); cy.get('@finish-filter').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); @@ -214,7 +230,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should open PreRequisite dropdown and expect it be lazily loaded', () => { - cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(10)').as('checkbox10-header'); + cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(11)').as('checkbox10-header'); cy.get('@checkbox10-header').click(); cy.get('[data-test="alert-lazy"]').should('be.visible'); cy.get('[data-name="filter-prerequisites"] .ms-loading span').contains('Loading...'); @@ -225,7 +241,7 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over header-row (filter) Prerequisite column and expect to see tooltip of selected filter options', () => { - cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(10)').as('checkbox10-header'); + cy.get('.slick-headerrow-columns .slick-headerrow-column:nth(11)').as('checkbox10-header'); cy.get('@checkbox10-header').trigger('mouseover'); cy.get('.filter-prerequisites .ms-choice span').contains('15 of 1000 selected'); @@ -259,25 +275,57 @@ describe('Example 33 - Regular & Custom Tooltips', () => { }); it('should mouse over header title on 2nd column with Finish name and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get('.slick-header-columns .slick-header-column:nth(8)').as('finish-header'); + cy.get('.slick-header-columns .slick-header-column:nth(9)').as('finish-header'); cy.get('@finish-header').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); cy.get('@finish-header').trigger('mouseout'); }); + it('should mouse over "Filters Empty Description" button and expect global tooltip to show with title text', () => { + // Test button tooltip + cy.get('[data-test="filter-empty-desc"]').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'Apply filter to show only empty descriptions'); + cy.get('[data-test="filter-empty-desc"]').trigger('mouseout'); + + // Test icon tooltip + cy.get('[data-test="filter-empty-desc"] i.mdi').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'icon tooltip for empty descriptions'); + cy.get('[data-test="filter-empty-desc"] i.mdi').trigger('mouseout'); + + // Verify tooltip is hidden when hovering on another element + cy.get('[data-test="server-delay"]').trigger('mouseover'); + cy.get('.slick-custom-tooltip').should('not.exist'); + }); + + it('should mouse over "Filters Non-Empty Description" button and expect global tooltip to show with title text', () => { + // Test button tooltip + cy.get('[data-test="filter-non-empty-desc"]').trigger('mouseover'); + cy.wait(50); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'Apply filter to show only non-empty descriptions'); + cy.get('[data-test="filter-non-empty-desc"]').trigger('mouseout'); + + // Test icon tooltip + cy.get('[data-test="filter-non-empty-desc"] i.mdi').trigger('mouseover'); + cy.wait(10); + cy.get('.slick-custom-tooltip').should('be.visible'); + cy.get('.slick-custom-tooltip .tooltip-body').should('contain', 'icon tooltip for non-empty descriptions'); + cy.get('[data-test="filter-non-empty-desc"] i.mdi').trigger('mouseout'); + cy.wait(10); + cy.get('.slick-custom-tooltip').should('not.exist'); + }); + it('should click Prerequisite editor of 1st row (Task 2) and expect Task1 & 2 to be selected in the multiple-select drop', () => { - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(10)`).as('prereq-cell'); + cy.get('[data-row="0"] > .slick-cell:nth(11)').as('prereq-cell'); cy.get('@prereq-cell').should('contain', 'Task 2, Task 1').click(); cy.get('div.ms-drop[data-name=editor-prerequisites]').find('li.selected').should('have.length', 2); - cy.get('div.ms-drop[data-name=editor-prerequisites]').find('li.selected:nth(0) span').should('contain', 'Task 1'); - cy.get('div.ms-drop[data-name=editor-prerequisites]').find('li.selected:nth(1) span').should('contain', 'Task 2'); - cy.get('div.ms-drop[data-name=editor-prerequisites]').find('.ms-ok-button').click(); - cy.get('div.ms-drop[data-name=editor-prerequisites]').should('not.exist'); }); }); diff --git a/demos/react/test/cypress/e2e/example44.cy.ts b/demos/react/test/cypress/e2e/example44.cy.ts index ab4ee2fcf1..f399ed0364 100644 --- a/demos/react/test/cypress/e2e/example44.cy.ts +++ b/demos/react/test/cypress/e2e/example44.cy.ts @@ -1,4 +1,4 @@ -describe('Example 44 - Column & Row Span', { retries: 1 }, () => { +describe('Example 44 - Column & Row Span', { retries: 0 }, () => { const GRID_ROW_HEIGHT = 30; const fullTitles = [ 'Title', @@ -463,4 +463,102 @@ describe('Example 44 - Column & Row Span', { retries: 1 }, () => { cy.get('[data-row=499] > .slick-cell.l5.r5.active').should('have.length', 1); }); }); + + describe('Hide Columns with colspan/rowspan', () => { + it('should hide Title column and expect other colspan/rowspan to simply move over and stay attached to same columns', () => { + cy.get('[data-row=499] > .slick-cell.l5.r5.active').type('{ctrl}{home}', { release: false }); + cy.get('[data-row=2] > .slick-cell.l0.r0.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + + cy.get('.slick-header-column:nth(1)').trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children('li:nth-child(1)') + .children('label') + .should('contain', 'Title') + .click(); + cy.get('.slick-column-picker .close').click(); + + // Task 2 rowspan should be hidden now + cy.get('[data-row=2] > .slick-cell.l0.r0.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.not.eq(GRID_ROW_HEIGHT * 3) + ); + }); + + it('should start at "Revenue Growth" second cell down, then type "Arrow Right" key 2x times and expect 4th row "Policy Index" green section to still have a rowspan 3x and colspan of 4x', () => { + cy.get('[data-row=3] > .slick-cell.l1.r1.rowspan').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l1.r1.active').should('have.length', 1); + cy.get('@active_cell').type('{rightarrow}{rightarrow}'); + + cy.get('[data-row=2] > .slick-cell.l3.r5').should('not.have.class', 'rowspan'); + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + }); + + it('should go up by 1x "Arrow Up" and expect blue section colspan of 3x', () => { + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').type('{uparrow}'); + cy.get('[data-row=2] > .slick-cell.l3.r5.active').should('not.have.class', 'rowspan'); + }); + + it('should "Revenue Growth" rowspan should now be at first column and "Policy Index" should now be at third column', () => { + cy.get(`[data-row=0] > .slick-cell.l0.r0`).should('not.exist'); + cy.get(`[data-row=1] > .slick-cell.l0.r0`).should('not.exist'); + cy.get(`[data-row=2] > .slick-cell.l0.r0`).should('not.exist'); + + cy.get('[data-row=0] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=3] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) + ); + cy.get('[data-row=8] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 80) + ); + + cy.get('[data-row=2] > .slick-cell.l3.r5').should('not.have.class', 'rowspan'); + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=8] > .slick-cell.l3.r4.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 492) + ); + }); + + it('should show again "Title" column and expect "Revenue Growth" and "Policy Index" columns to be moved to the right by 1 column', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children('li:nth-child(1)') + .children('label') + .should('contain', 'Title') + .click(); + cy.get('.slick-column-picker .close').click(); + + cy.get(`[data-row=0] > .slick-cell.l0.r0`).should('contain', 'Task 0'); + cy.get(`[data-row=1] > .slick-cell.l0.r0`).should('contain', 'Task 1'); + cy.get(`[data-row=2] > .slick-cell.l0.r0`).should('contain', 'Task 2'); + + cy.get('[data-row=0] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=3] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) + ); + cy.get('[data-row=8] > .slick-cell.l1.r1.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 80) + ); + + cy.get('[data-row=2] > .slick-cell.l3.r5').should('not.have.class', 'rowspan'); + cy.get('[data-row=3] > .slick-cell.l3.r7.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('[data-row=8] > .slick-cell.l3.r4.rowspan').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 492) + ); + }); + }); }); diff --git a/demos/react/test/cypress/e2e/example48.cy.ts b/demos/react/test/cypress/e2e/example48.cy.ts index a9df9df6b5..40d990bb34 100644 --- a/demos/react/test/cypress/e2e/example48.cy.ts +++ b/demos/react/test/cypress/e2e/example48.cy.ts @@ -160,19 +160,17 @@ describe('Example 48 - Hybrid Selection Model', () => { }); it('should click on row 4 and 5 row checkbox and expect 5 full rows to be selected', () => { - cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l0.r0').as('task4'); cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l1.r1').should('contain', '4'); - cy.get('@task4').click(); + cy.get('#grid48-2 .slick-row[data-row="4"] input[type=checkbox]').click({ force: true }); cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 1); // select another row - cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l0.r0').as('task5'); cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l1.r1').should('contain', '5'); - cy.get('@task5').click(); + cy.get('#grid48-2 .slick-row[data-row="5"] input[type=checkbox]').click({ force: true }); cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); - cy.get('@task5').should('have.class', 'selected'); + cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 2); }); }); diff --git a/demos/react/test/cypress/e2e/example51.cy.ts b/demos/react/test/cypress/e2e/example51.cy.ts new file mode 100644 index 0000000000..80e5148c97 --- /dev/null +++ b/demos/react/test/cypress/e2e/example51.cy.ts @@ -0,0 +1,382 @@ +import { format } from '@formkit/tempo'; + +describe('Example 51 - Menus with Slots', () => { + const fullTitles = ['Title', 'Duration', 'Start', 'Finish', 'Cost', '% Complete', 'Action']; + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/example51`); + cy.get('h2').should('contain', 'Example 51: Menus with Slots'); + }); + + it('should have exact column titles in the grid', () => { + cy.get('#grid51') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should open Context Menu hover "Duration" column and expect built-in and custom items listed in specific order', () => { + cy.get('[data-row="0"] > .slick-cell:nth(2)').rightclick({ force: true }); + + // 1st item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('.edit-cell-icon').contains('โœŽ'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item span').contains('Edit Cell'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('kbd.edit-cell').contains('F2'); + + // icon should rotate while hovering + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0)').trigger('mouseover'); + cy.wait(175); // wait for rotation + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item .edit-cell-icon') + .invoke('css', 'transform') // Get the transform property + .then((cssTransform) => { + const transformValue = cssTransform as unknown as string; // Cast to string + cy.getTransformValue(transformValue, true, 'rotate').then((rotationAngle) => { + expect(rotationAngle).to.approximately(13, 15); // 15 degrees rotation + }); + }); + + // 2nd item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item').find('i.mdi-content-copy').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item').find('span.menu-item-label').contains('Copy'); + + // 3rd item - divider + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(2)').should('have.class', 'slick-menu-item-divider'); + + // 4th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(3)').should('have.class', 'slick-menu-item-disabled'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-close').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item') + .find('span.menu-item-label') + .contains('Clear all Grouping'); + + // 5th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-disabled'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item').find('i.mdi-arrow-collapse').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item') + .find('span.menu-item-label') + .contains('Collapse all Groups'); + + // 6th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(5)').should('have.class', 'slick-menu-item-disabled'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item').find('i.mdi-arrow-expand').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('span.menu-item-label') + .contains('Expand all Groups'); + + // 7th item - divider + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(6)').should('have.class', 'slick-menu-item-divider'); + + // 8th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item').find('i.mdi-download').should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item') + .find('span.menu-item-label') + .contains('Export'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(7)').find('.sub-item-chevron').should('exist'); + + // 9th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(8)').should('have.class', 'slick-menu-item-divider'); + + // 10th item + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(9) .menu-item') + .find('i.mdi-delete.text-danger') + .should('exist'); + cy.get('.slick-context-menu .slick-menu-command-list .slick-menu-item:nth(9) .menu-item') + .find('span.menu-item-label') + .contains('Delete Row'); + }); + + it('should open Export->Excel context sub-menu', () => { + const subCommands1 = ['Export as Excel', 'Export as CSV', 'Export as PDF']; + + const stub = cy.stub(); + cy.on('window:alert', stub); + + cy.get('[data-row="0"] > .slick-cell:nth(2)').should('contain', '0'); + cy.get('[data-row="0"] > .slick-cell:nth(2)').rightclick({ force: true }); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains(/^Export$/) + .trigger('mouseover'); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .menu-item') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + // click different sub-menu + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains('Export as Excel') + .should('exist') + .click() + .then(() => expect(stub.getCall(0)).to.be.calledWith('Export to Excel')); + + cy.get('.slick-submenu').should('have.length', 0); + }); + + it('should open Header Menu from the "Title" column and expect some commands to have keyboard hints on the right side', () => { + cy.get('.slick-header-column:nth(0)').trigger('mouseover', { force: true }); + cy.get('.slick-header-column:nth(0)').children('.slick-header-menu-button').invoke('show').click(); + + // 1st item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('i.mdi-arrow-expand-horizontal') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('span.menu-item-label') + .contains('Resize by Content'); + + // 2nd item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(1)').should('have.class', 'slick-menu-item-divider'); + + // 3rd item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('i.mdi-sort-ascending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item') + .find('span.menu-item-label') + .contains('Sort Ascending'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('kbd.key-hint').contains('Alt+โ†‘'); + + // 4th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-sort-descending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item') + .find('span.menu-item-label') + .contains('Sort Descending'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('kbd.key-hint').contains('Alt+โ†“'); + + // 5th item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-divider'); + + // 6th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('i.mdi-filter-remove-outline') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('span.menu-item-label') + .contains('Remove Filter'); + + // 7th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('i.mdi-sort-variant-off').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item') + .find('span.menu-item-label') + .contains('Remove Sort'); + + // 8th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item').find('i.mdi-close').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item') + .find('span.menu-item-label') + .contains('Hide Column'); + }); + + it('should open Header Menu from the "Duration" column and expect some commands to have tags on the right side', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover', { force: true }); + cy.get('.slick-header-column:nth(1)').children('.slick-header-menu-button').invoke('show').click(); + + // 1st item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('i.mdi-arrow-expand-horizontal') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('span.menu-item-label') + .contains('Resize by Content'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('span.key-hint.danger').contains('NEW'); + + // 2nd item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(1)').should('have.class', 'slick-menu-item-divider'); + + // 3rd item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('i.mdi-sort-ascending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item') + .find('span.menu-item-label') + .contains('Sort Ascending'); + + // 4th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-sort-descending').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item') + .find('span.menu-item-label') + .contains('Sort Descending'); + + // 5th item - divider + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-divider'); + + // 6th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('i.mdi-filter-remove-outline') + .should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('span.menu-item-label') + .contains('Remove Filter'); + + // 7th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('i.mdi-sort-variant-off').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item') + .find('span.menu-item-label') + .contains('Remove Sort'); + + // 8th item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item').find('i.mdi-close').should('exist'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(7) .menu-item') + .find('span.menu-item-label') + .contains('Hide Column'); + }); + + it('should open Header Menu from the "Cost" column and expect first item to have a dynamic tooltip timestamp when hovering', () => { + cy.get('#grid51').find('.slick-header-column:nth(4)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + // 1st item + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('.advanced-export-icon').contains('๐Ÿ“Š'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').contains('Advanced Export'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('kbd.key-hint').contains('Ctrl+E'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').should( + 'have.css', + 'background-color', + 'rgba(0, 0, 0, 0)' + ); + + // icon should scale up + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').trigger('mouseover'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item .advanced-export-icon') + .invoke('css', 'transform') + .then((cssTransform) => { + const transformValue = cssTransform as unknown as string; // Cast to string + cy.getTransformValue(transformValue, true, 'scale').then((scaleValue) => { + expect(scaleValue).to.be.approximately(1.1, 1.15); // Check the scale value if applied + }); + }); + + const today = format(new Date(), 'YYYY-MM-DD'); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').should( + 'have.css', + 'background-color', + 'rgb(133, 70, 133)' + ); + cy.get('.slick-custom-tooltip').contains(`๐Ÿ“ˆ Export timestamp: ${today}`); + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(0)').trigger('mouseout'); + cy.get('.slick-custom-tooltip').should('not.exist'); + cy.get('body').click(); + }); + + it('should open Action Menu from last column "Action" column and expect custom items listed in specific order', () => { + cy.get('[data-row="1"] > .slick-cell:nth(6)').click(); + cy.get('.slick-command-header.with-title.with-close').contains('Cell Actions'); + + // 1st item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item').find('.mdi-content-copy').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item span').contains('Copy Cell Value'); + + // 2nd item - divider + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(1)').should('have.class', 'slick-menu-item-divider'); + + // 3rd item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').find('.mdi-download').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item span').contains('Export Row'); + + // 4th item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('i.mdi-download').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').find('span.menu-item-label').contains('Export'); + + // 5th item - divider + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(4)').should('have.class', 'slick-menu-item-divider'); + + // 6th item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item').find('.edit-cell-icon').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item .edit-cell-icon').contains('โœŽ'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(5)').should('have.css', 'background-color', 'rgba(0, 0, 0, 0)'); + + // 7th item + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('.mdi-delete.text-danger').should('exist'); + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item span').contains('Delete Row'); + }); + + it('should open Export->Excel cell sub-menu', () => { + const subCommands1 = ['Export as Excel', 'Export as CSV', 'Export as PDF']; + + const stub = cy.stub(); + cy.on('window:alert', stub); + + cy.get('.slick-cell-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains(/^Export$/) + .trigger('mouseover'); + + cy.get('.slick-cell-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .menu-item') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + // click different sub-menu + cy.get('.slick-cell-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .menu-item') + .contains('Export as Excel') + .should('exist') + .click() + .then(() => expect(stub.getCall(0)).to.be.calledWith('Export row #1 to Excel')); + + cy.get('.slick-submenu').should('have.length', 0); + }); + + it('should open Grid Menu and expect built-in commands first then custom items listed in specific order', () => { + cy.get('.slick-grid-menu-button.mdi-menu').click(); + + // 1st item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item') + .find('.mdi-filter-remove-outline.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(0) .menu-item span').contains('Clear all Filters'); + + // 2nd item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item') + .find('.mdi-sort-variant-off.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(1) .menu-item span').contains('Clear all Sorting'); + + // 3rd item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item') + .find('.mdi-flip-vertical.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item span').contains('Toggle Filter Row'); + + // 4th item - divider + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(3)').should('have.class', 'slick-menu-item-divider'); + + // 5th item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item') + .find('.mdi-file-excel-outline.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(4) .menu-item span').contains('Export to Excel'); + + // 6th item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item') + .find('.mdi-download.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item span').contains('Export to CSV'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(5) .menu-item').find('span.key-hint.warn').contains('CUSTOM'); + + // 7th item + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item') + .find('.mdi-refresh.menu-item-icon') + .should('exist'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item span').contains('Refresh Data'); + cy.get('.slick-grid-menu .slick-menu-command-list .slick-menu-item:nth(6) .menu-item').find('kbd.key-hint').contains('F5'); + }); + + it('should sort ascending "Duration" even though the header menu item was override without an action callback', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(2) .menu-item').should('contain', 'Sort Ascending').click(); + + cy.get('[data-row=0]').children('.slick-cell:nth(1)').should('contain', '0'); + cy.get('[data-row=1]').children('.slick-cell:nth(1)').should('contain', '0'); + cy.get('[data-row=2]').children('.slick-cell:nth(1)').should('contain', '0'); + }); + + it('should sort descending "Duration" even though the header menu item was override without an action callback', () => { + cy.get('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + cy.get('.slick-header-menu .slick-menu-command-list .slick-menu-item:nth(3) .menu-item').should('contain', 'Sort Descending').click(); + + cy.get('[data-row=0]').children('.slick-cell:nth(1)').should('contain', '100'); + cy.get('[data-row=1]').children('.slick-cell:nth(1)').should('contain', '100'); + cy.get('[data-row=2]').children('.slick-cell:nth(1)').should('contain', '100'); + }); +}); diff --git a/demos/react/test/cypress/support/commands.ts b/demos/react/test/cypress/support/commands.ts index a35be6228b..3be5a3685b 100644 --- a/demos/react/test/cypress/support/commands.ts +++ b/demos/react/test/cypress/support/commands.ts @@ -47,6 +47,7 @@ declare global { ): Chainable>; saveLocalStorage: () => void; restoreLocalStorage: () => void; + getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable; } } } @@ -86,3 +87,35 @@ Cypress.Commands.add('restoreLocalStorage', () => { localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]); }); }); + +Cypress.Commands.add( + 'getTransformValue', + ( + cssTransformMatrix: string, + absoluteValue: boolean, + transformType: 'rotate' | 'scale' = 'rotate' // Default to 'rotate' + ): Cypress.Chainable => { + if (!cssTransformMatrix || cssTransformMatrix === 'none') { + throw new Error('Transform matrix is undefined or none'); + } + + const cssTransformMatrixIndexes = cssTransformMatrix.split('(')[1].split(')')[0].split(','); + + if (transformType === 'rotate') { + const cssTransformScale = Math.sqrt( + +cssTransformMatrixIndexes[0] * +cssTransformMatrixIndexes[0] + +cssTransformMatrixIndexes[1] * +cssTransformMatrixIndexes[1] + ); + + const cssTransformSin = +cssTransformMatrixIndexes[1] / cssTransformScale; + const cssTransformAngle = Math.round(Math.asin(cssTransformSin) * (180 / Math.PI)); + + return cy.wrap(absoluteValue ? Math.abs(cssTransformAngle) : cssTransformAngle); + } else if (transformType === 'scale') { + // Assuming scale is based on the first value in the matrix. + const scaleValue = +cssTransformMatrixIndexes[0]; // First value typically represents scaling in x direction. + return cy.wrap(scaleValue); // Directly return the scale value. + } + + throw new Error('Unsupported transform type'); + } +); diff --git a/demos/react/tsconfig.json b/demos/react/tsconfig.json index 68ce43f7d8..669403216e 100644 --- a/demos/react/tsconfig.json +++ b/demos/react/tsconfig.json @@ -4,7 +4,6 @@ "target": "es2022", "module": "esnext", "sourceMap": true, - "downlevelIteration": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "esModuleInterop": true, diff --git a/demos/vanilla/CHANGELOG.md b/demos/vanilla/CHANGELOG.md index 0d4cafd829..3c3d9f0ab0 100644 --- a/demos/vanilla/CHANGELOG.md +++ b/demos/vanilla/CHANGELOG.md @@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [10.0.0-beta.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v10.0.0-beta.0) (2026-02-14) + +### โš  BREAKING CHANGES + +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag (#2331) +* drop Cell/Row Selection Models & keep only HybridSelectionModel (#2330) +* remove all Deprecated code (#2302) +* drop OperatorType enums and keep only type literal (#2301) +* replacing multiple TypeScript `enum` with `type` to decrease build size (#2300) +* switch to column `hidden` property and always keep all columns (#2281) + +### Features + +* add custom menu slot renderers ([#2375](https://github.com/ghiscoding/slickgrid-universal/issues/2375)) ([7ebbda5](https://github.com/ghiscoding/slickgrid-universal/commit/7ebbda58233bb5ce94b81b5b2b578af0ea6a068d)) - by @ghiscoding +* auto-enabled external resources with their associated flags ([#2362](https://github.com/ghiscoding/slickgrid-universal/issues/2362)) ([16dd8a7](https://github.com/ghiscoding/slickgrid-universal/commit/16dd8a77dd5d136a5a99321f0fc4c50571fdb0c0)) - by @ghiscoding +* drop Cell/Row Selection Models & keep only HybridSelectionModel ([#2330](https://github.com/ghiscoding/slickgrid-universal/issues/2330)) ([4398cf4](https://github.com/ghiscoding/slickgrid-universal/commit/4398cf42e03f6971b81db8cea4ed11138c0aa452)) - by @ghiscoding +* migrate Row/Hybrid Selection flag into a single `enableSelection` flag ([#2331](https://github.com/ghiscoding/slickgrid-universal/issues/2331)) ([5be5e6a](https://github.com/ghiscoding/slickgrid-universal/commit/5be5e6a862ecd024cf43d404769d65c6c1dd335e)) - by @ghiscoding +* switch to column `hidden` property and always keep all columns ([#2281](https://github.com/ghiscoding/slickgrid-universal/issues/2281)) ([075c649](https://github.com/ghiscoding/slickgrid-universal/commit/075c64961cb7400500df46b792866d39fba2d9e0)) - by @ghiscoding +* **tooltip:** add global tooltip observation for non-grid elements ([#2371](https://github.com/ghiscoding/slickgrid-universal/issues/2371)) ([1bbc8de](https://github.com/ghiscoding/slickgrid-universal/commit/1bbc8de895e370843286eadd08574efc552ad8fd)) - by @ghiscoding + +### Bug Fixes + +* **plugin:** SlickCustomTooltip should work with parent+child tooltips ([#2374](https://github.com/ghiscoding/slickgrid-universal/issues/2374)) ([8af7f45](https://github.com/ghiscoding/slickgrid-universal/commit/8af7f45eb19f0a00da2f3de7c729504be7d043eb)) - by @ghiscoding +* remove all Deprecated code ([#2302](https://github.com/ghiscoding/slickgrid-universal/issues/2302)) ([f42c46c](https://github.com/ghiscoding/slickgrid-universal/commit/f42c46cd1f05b5c72c62f552f124b5bfe776f8b0)) - by @ghiscoding + +### Code Refactoring + +* drop OperatorType enums and keep only type literal ([#2301](https://github.com/ghiscoding/slickgrid-universal/issues/2301)) ([5dd0807](https://github.com/ghiscoding/slickgrid-universal/commit/5dd08079460dc9af798ab29527997a6d4b31abdd)) - by @ghiscoding +* replacing multiple TypeScript `enum` with `type` to decrease build size ([#2300](https://github.com/ghiscoding/slickgrid-universal/issues/2300)) ([ea79395](https://github.com/ghiscoding/slickgrid-universal/commit/ea79395cf663b3abce8e43cf27ba6ffea7cfe113)) - by @ghiscoding + ## [9.13.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.12.0...v9.13.0) (2026-01-30) ### Features diff --git a/demos/vanilla/package.json b/demos/vanilla/package.json index 4aaf764118..606bae1b01 100644 --- a/demos/vanilla/package.json +++ b/demos/vanilla/package.json @@ -1,7 +1,7 @@ { "name": "vanilla-demo", "private": true, - "version": "9.13.0", + "version": "10.0.0-beta.0", "scripts": { "build": "pnpm type-check && vite build", "type-check": "tsc --noEmit", @@ -38,6 +38,6 @@ "@types/node": "catalog:", "sass": "catalog:", "typescript": "catalog:", - "vite": "catalog:vite7" + "vite": "catalog:" } } diff --git a/demos/vanilla/src/app-routing.ts b/demos/vanilla/src/app-routing.ts index 54de24e7ce..3a557e7581 100644 --- a/demos/vanilla/src/app-routing.ts +++ b/demos/vanilla/src/app-routing.ts @@ -37,6 +37,7 @@ import Example36 from './examples/example36.js'; import Example37 from './examples/example37.js'; import Example38 from './examples/example38.js'; import Example39 from './examples/example39.js'; +import Example40 from './examples/example40.js'; import Icons from './examples/icons.js'; import type { RouterConfig } from './interfaces.js'; @@ -84,6 +85,7 @@ export class AppRouting { { route: 'example37', name: 'example37', view: './examples/example37.html', viewModel: Example37, title: 'Example37' }, { route: 'example38', name: 'example38', view: './examples/example38.html', viewModel: Example38, title: 'Example38' }, { route: 'example39', name: 'example39', view: './examples/example39.html', viewModel: Example39, title: 'Example39' }, + { route: 'example40', name: 'example40', view: './examples/example40.html', viewModel: Example40, title: 'Example40' }, { route: '', redirect: 'example01' }, { route: '**', redirect: 'example01' }, ]; diff --git a/demos/vanilla/src/app.html b/demos/vanilla/src/app.html index e665df91ee..5e7542fc5a 100644 --- a/demos/vanilla/src/app.html +++ b/demos/vanilla/src/app.html @@ -79,6 +79,7 @@

      Slickgrid-Universal

      Example37 - Hybrid Selection Model Example38 - Spreadsheet Drag-Fill Example39 - Master/Detail Grids + Example40 - Menus with Slots

    diff --git a/demos/vanilla/src/examples/example01.ts b/demos/vanilla/src/examples/example01.ts index 92cc438584..6f6a086e78 100644 --- a/demos/vanilla/src/examples/example01.ts +++ b/demos/vanilla/src/examples/example01.ts @@ -1,4 +1,4 @@ -import { ExtensionName, Formatters, type Column, type GridOption } from '@slickgrid-universal/common'; +import { Formatters, type Column, type GridOption } from '@slickgrid-universal/common'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { ExampleGridOptions } from './example-grid-options.js'; import { zeroPadding } from './utilities.js'; @@ -266,7 +266,7 @@ export default class Example01 { toggleGridMenu(e: MouseEvent) { if (this.sgb2?.extensionService) { - const gridMenuInstance = this.sgb2.extensionService.getExtensionInstanceByName(ExtensionName.gridMenu); + const gridMenuInstance = this.sgb2.extensionService.getExtensionInstanceByName('gridMenu'); // open the external button Grid Menu, you can also optionally pass Grid Menu options as 2nd argument // for example we want to align our external button on the right without affecting the menu within the grid which will stay aligned on the left gridMenuInstance.showGridMenu(e, { dropSide: 'right' }); diff --git a/demos/vanilla/src/examples/example03.ts b/demos/vanilla/src/examples/example03.ts index 9dbc265d3a..c2a023b836 100644 --- a/demos/vanilla/src/examples/example03.ts +++ b/demos/vanilla/src/examples/example03.ts @@ -355,9 +355,6 @@ export default class Example03 { enableAutoSizeColumns: true, enableAutoResize: true, enableCellNavigation: true, - enablePdfExport: true, - enableTextExport: true, - enableExcelExport: true, excelExportOptions: { exportWithFormatter: true, }, @@ -367,8 +364,12 @@ export default class Example03 { repeatHeadersOnEachPage: true, // defaults to true }, externalResources: [new TextExportService(), this.excelExportService, this.pdfExportService], + // -- NOTE: registered resources are auto-enabled + // enableTextExport: true, + // enablePdfExport: true, + // enableExcelExport: true, enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -406,7 +407,7 @@ export default class Example03 { grouping: ['duration'], }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, @@ -423,7 +424,7 @@ export default class Example03 { onCommand: (e, args) => this.executeCommand(e, args), onOptionSelected: (_e, args) => { // change "Effort-Driven" property with new option selected from the Cell Menu - const dataContext = args && args.dataContext; + const dataContext = args?.dataContext; if (dataContext && dataContext.hasOwnProperty('effortDriven')) { dataContext.effortDriven = args.item.option; this.sgb.gridService.updateItem(dataContext); diff --git a/demos/vanilla/src/examples/example04.ts b/demos/vanilla/src/examples/example04.ts index 3c1145de26..ffb5bb5b86 100644 --- a/demos/vanilla/src/examples/example04.ts +++ b/demos/vanilla/src/examples/example04.ts @@ -3,7 +3,6 @@ import { Editors, Filters, Formatters, - OperatorType, type AutocompleterOption, type Column, type ColumnEditorDualInput, @@ -123,7 +122,7 @@ export default class Example04 { collectionFilterBy: { property: 'value', value: 0, - operator: OperatorType.notEqual, + operator: '!=', }, // collectionOverride: (updatedCollection, args) => { // console.log(args); @@ -404,7 +403,7 @@ export default class Example04 { sanitizeDataExport: true, }, externalResources: [new ExcelExportService()], - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -415,7 +414,7 @@ export default class Example04 { name: 'Sel', // column name will only show when `hideInColumnTitleRow` is true onExtensionRegistered: (instance) => (this.checkboxSelectorInstance = instance), }, - enableRowSelection: true, + enableSelection: true, frozenColumn: this.frozenColumnCount, frozenRow: this.frozenRowCount, // frozenBottom: true, // if you want to freeze the bottom instead of the top, you can enable this property diff --git a/demos/vanilla/src/examples/example05.ts b/demos/vanilla/src/examples/example05.ts index c38ef8d268..5c9a12a0f1 100644 --- a/demos/vanilla/src/examples/example05.ts +++ b/demos/vanilla/src/examples/example05.ts @@ -261,7 +261,7 @@ export default class Example05 { '
    Grid created with Slickgrid-Universal
    ', }, // enableCheckboxSelector: true, - // enableRowSelection: true, + // enableSelection: true, // multiSelect: false, // checkboxSelector: { // hideInFilterHeaderRow: false, diff --git a/demos/vanilla/src/examples/example07.ts b/demos/vanilla/src/examples/example07.ts index 36504c070e..d8d1cefb2f 100644 --- a/demos/vanilla/src/examples/example07.ts +++ b/demos/vanilla/src/examples/example07.ts @@ -1,5 +1,5 @@ import { BindingEventService } from '@slickgrid-universal/binding'; -import { Editors, Filters, Formatters, OperatorType, type Column, type GridOption } from '@slickgrid-universal/common'; +import { Editors, Filters, Formatters, type Column, type GridOption } from '@slickgrid-universal/common'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import DOMPurify from 'dompurify'; @@ -355,7 +355,7 @@ export default class Example07 { separatorBetweenTextLabels: ' ', }, model: Filters.multipleSelect, - operator: OperatorType.inContains, + operator: 'IN_CONTAINS', }, }, ]; @@ -385,8 +385,8 @@ export default class Example07 { externalResources: [new ExcelExportService()], enableCellNavigation: true, enableCheckboxSelector: true, - enableRowSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, diff --git a/demos/vanilla/src/examples/example08.html b/demos/vanilla/src/examples/example08.html index 490c1b4bc9..83aefa28d1 100644 --- a/demos/vanilla/src/examples/example08.html +++ b/demos/vanilla/src/examples/example08.html @@ -12,7 +12,17 @@

    -

    Grid 1 (with Header Grouping & Colspan)

    +

    + Grid 1 (with Header Grouping & ColSpan) + +

    diff --git a/demos/vanilla/src/examples/example08.ts b/demos/vanilla/src/examples/example08.ts index 1f761ad988..4098b5200b 100644 --- a/demos/vanilla/src/examples/example08.ts +++ b/demos/vanilla/src/examples/example08.ts @@ -1,4 +1,4 @@ -import { type Column, type GridOption, type ItemMetadata, type OperatorString } from '@slickgrid-universal/common'; +import { type Column, type GridOption, type ItemMetadata, type OperatorType } from '@slickgrid-universal/common'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { TextExportService } from '@slickgrid-universal/text-export'; @@ -16,9 +16,10 @@ export default class Example08 { sgb1: SlickVanillaGridBundle; sgb2: SlickVanillaGridBundle; grid2SearchSelectedColumn: Column; - grid2SelectedOperator: OperatorString; + grid2SelectedOperator: OperatorType; grid2SearchValue: any; - operatorList: OperatorString[] = ['=', '<', '<=', '>', '>=', '<>', 'StartsWith', 'EndsWith']; + operatorList: OperatorType[] = ['=', '<', '<=', '>', '>=', '<>', 'StartsWith', 'EndsWith']; + isColspanSpreading = false; constructor() { this.definedGrid1(); @@ -97,6 +98,7 @@ export default class Example08 { getRowMetadata: (item: any, row: number) => this.renderDifferentColspan(item, row), }, }, + spreadHiddenColspan: this.isColspanSpreading, }; } @@ -251,7 +253,7 @@ export default class Example08 { } selectedOperatorChanged(newOperator: string) { - this.grid2SelectedOperator = newOperator as OperatorString; + this.grid2SelectedOperator = newOperator as OperatorType; this.updateFilter(); } @@ -265,10 +267,17 @@ export default class Example08 { this.updateFilter(); } + spreadColspan() { + this.isColspanSpreading = !this.isColspanSpreading; + this.sgb1.slickGrid?.setOptions({ spreadHiddenColspan: this.isColspanSpreading }); + this.sgb1.slickGrid?.resetActiveCell(); + this.sgb1.slickGrid?.invalidate(); + } + updateFilter() { this.sgb2.filterService.updateSingleFilter({ columnId: `${this.grid2SearchSelectedColumn?.id ?? ''}`, - operator: this.grid2SelectedOperator as OperatorString, + operator: this.grid2SelectedOperator as OperatorType, searchTerms: [this.grid2SearchValue || ''], }); } diff --git a/demos/vanilla/src/examples/example09.ts b/demos/vanilla/src/examples/example09.ts index dc87fbc457..bb24dd0732 100644 --- a/demos/vanilla/src/examples/example09.ts +++ b/demos/vanilla/src/examples/example09.ts @@ -1,7 +1,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Filters, - OperatorType, type Column, type CurrentColumn, type GridOption, @@ -141,13 +140,13 @@ export default class Example09 { hideInColumnTitleRow: true, }, compoundOperatorAltTexts: { - // where '=' is any of the `OperatorString` type shown above + // where '=' is any of the `OperatorType` type shown above text: { Custom: { operatorAlt: '%%', descAlt: 'SQL Like' } }, }, enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enablePagination: true, // you could optionally disable the Pagination pagination: { pageSizes: [10, 20, 50, 100, 500, 50000], @@ -158,8 +157,8 @@ export default class Example09 { : { // you can also type operator as string, e.g.: operator: 'EQ' filters: [ - // { columnId: 'name', searchTerms: ['w'], operator: OperatorType.startsWith }, - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'name', searchTerms: ['w'], operator: 'StartsWith' }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, ], sorters: [ // direction can be written as 'asc' (uppercase or lowercase) and/or use the SortDirection type @@ -174,7 +173,7 @@ export default class Example09 { enableSelect: this.isSelectEnabled, enableExpand: this.isExpandEnabled, filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { let matchesSearch = searchValues[0].replace(/\*/g, '.*'); matchesSearch = matchesSearch.slice(0, 1) + CARET_HTML_ESCAPED + matchesSearch.slice(1); matchesSearch = matchesSearch.slice(0, -1) + "$'"; @@ -483,7 +482,7 @@ export default class Example09 { setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.sgb?.filterService.updateFilters([ - // { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'gender', searchTerms: ['male'], operator: '=' }, { columnId: 'name', searchTerms: ['A'], operator: 'a*' }, ]); } diff --git a/demos/vanilla/src/examples/example10.ts b/demos/vanilla/src/examples/example10.ts index 21f7ecdafd..fe2e2525fc 100644 --- a/demos/vanilla/src/examples/example10.ts +++ b/demos/vanilla/src/examples/example10.ts @@ -3,7 +3,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Filters, Formatters, - OperatorType, type Column, type CursorPageInfo, type GridOption, @@ -207,7 +206,7 @@ export default class Example10 { gridHeight: 275, gridWidth: 900, compoundOperatorAltTexts: { - // where '=' is any of the `OperatorString` type shown above + // where '=' is any of the `OperatorType` type shown above text: { Custom: { operatorAlt: '%%', descAlt: 'SQL Like' } }, }, enableFiltering: true, @@ -235,13 +234,13 @@ export default class Example10 { ], filters: [ // you can use OperatorType or type them as string, e.g.: operator: 'EQ' - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, - // { columnId: 'name', searchTerms: ['John Doe'], operator: OperatorType.contains }, - { columnId: 'name', searchTerms: ['Joh*oe'], operator: OperatorType.startsWithEndsWith }, + { columnId: 'gender', searchTerms: ['male'], operator: 'EQ' }, + // { columnId: 'name', searchTerms: ['John Doe'], operator: 'Contains' }, + { columnId: 'name', searchTerms: ['Joh*oe'], operator: 'StartsWithEndsWith' }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }, // use a date range with 2 searchTerms values - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ], sorters: [ // direction can written as 'asc' (uppercase or lowercase) and/or use the SortDirection type @@ -263,7 +262,7 @@ export default class Example10 { }, ], filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { // technically speaking GraphQL isn't a database query language like SQL, it's an application query language. // What that means is that GraphQL won't let you write arbitrary queries out of the box. // It will only support the types of queries defined in your GraphQL schema. @@ -390,11 +389,11 @@ export default class Example10 { // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.sgb.filterService.updateFilters([ - { columnId: 'gender', searchTerms: ['female'], operator: OperatorType.equal }, - { columnId: 'name', searchTerms: ['Jane'], operator: OperatorType.startsWith }, + { columnId: 'gender', searchTerms: ['female'], operator: 'EQ' }, + { columnId: 'name', searchTerms: ['Jane'], operator: 'StartsWith' }, { columnId: 'company', searchTerms: ['acme'], operator: 'IN' }, - { columnId: 'billingAddressZip', searchTerms: ['11'], operator: OperatorType.greaterThanOrEqual }, - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'billingAddressZip', searchTerms: ['11'], operator: '>=' }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ]); } @@ -413,13 +412,13 @@ export default class Example10 { this.sgb?.filterService.updateFilters([ // you can use OperatorType or type them as string, e.g.: operator: 'EQ' - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, - // { columnId: 'name', searchTerms: ['John Doe'], operator: OperatorType.contains }, - { columnId: 'name', searchTerms: ['Joh*oe'], operator: OperatorType.startsWithEndsWith }, + { columnId: 'gender', searchTerms: ['male'], operator: 'EQ' }, + // { columnId: 'name', searchTerms: ['John Doe'], operator: 'Contains' }, + { columnId: 'name', searchTerms: ['Joh*oe'], operator: 'StartsWithEndsWith' }, { columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }, // use a date range with 2 searchTerms values - { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive }, + { columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: 'RangeInclusive' }, ]); this.sgb?.sortService.updateSorting([ // direction can written as 'asc' (uppercase or lowercase) and/or use the SortDirection type diff --git a/demos/vanilla/src/examples/example11.ts b/demos/vanilla/src/examples/example11.ts index 31234d908c..f183d81e2d 100644 --- a/demos/vanilla/src/examples/example11.ts +++ b/demos/vanilla/src/examples/example11.ts @@ -6,7 +6,6 @@ import { Filters, formatNumber, Formatters, - OperatorType, SlickGlobalEditorLock, SortComparers, type AutocompleterOption, @@ -87,11 +86,13 @@ export default class Example11 { isUserDefined: false, columns: [...this.allColumnIds] .map((colId) => ({ columnId: `${colId}` })) + // OR the `hidden` props alternative + // .map((colId) => ({ columnId: `${colId}`, hidden: false })) .filter((col) => col.columnId !== 'product' && col.columnId !== 'countryOfOrigin'), // remove "Product", "Country of Origin" filters: [ - { columnId: 'finish', operator: OperatorType.lessThanOrEqual, searchTerms: [`${this.currentYear}-01-01`] }, - { columnId: 'completed', operator: OperatorType.equal, searchTerms: [true] }, - { columnId: 'percentComplete', operator: OperatorType.greaterThan, searchTerms: [50] }, + { columnId: 'finish', operator: '<=', searchTerms: [`${this.currentYear}-01-01`] }, + { columnId: 'completed', operator: '=', searchTerms: [true] }, + { columnId: 'percentComplete', operator: '>', searchTerms: [50] }, ] as CurrentFilter[], sorters: [{ columnId: 'finish', direction: 'desc' }] as CurrentSorter[], }, @@ -100,7 +101,11 @@ export default class Example11 { value: 'greaterCurrentYear', isSelected: false, isUserDefined: false, - columns: [...this.allColumnIds].map((colId) => ({ columnId: `${colId}` })).filter((col) => col.columnId !== 'cost'), // remove "Cost" + columns: [...this.allColumnIds] + .map((colId) => ({ columnId: `${colId}` })) + // OR the `hidden` props alternative + // .map((colId) => ({ columnId: `${colId}`, hidden: false })) + .filter((col) => col.columnId !== 'cost'), // remove "Cost" filters: [{ columnId: 'finish', operator: '>=', searchTerms: [`${this.currentYear + 1}-01-01`] }], sorters: [{ columnId: 'finish', direction: 'asc' }] as CurrentSorter[], }, @@ -400,14 +405,14 @@ export default class Example11 { }, externalResources: [new ExcelExportService(), new SlickCustomTooltip()], enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, rowHeight: 33, headerRowHeight: 35, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, @@ -451,6 +456,10 @@ export default class Example11 { ], onCommand: (e, args) => this.executeCommand(e, args), }, + + // the `hidden` props alternative, we could use column "hidden" props, then we could also include it in the saved state + // using this flag (below) will result in all columns included in the Grid State including hidden columns + // gridStateIncludeHiddenProps: true, }; const storedData = localStorage.getItem(LOCAL_STORAGE_KEY); @@ -737,8 +746,7 @@ export default class Example11 { } this.predefinedViews.forEach((viewSelect) => (viewSelect.isSelected = false)); // reset selection - const currentGridState = this.sgb.gridStateService.getCurrentGridState(); - const { columns, filters, sorters, pinning } = currentGridState; + const { columns, filters, sorters, pinning } = this.sgb.gridStateService.getCurrentGridState(); const viewName = await prompt('Please provide a name for the new View.'); if (viewName) { @@ -792,8 +800,7 @@ export default class Example11 { event.stopPropagation(); return; } - const currentGridState = this.sgb.gridStateService.getCurrentGridState(); - const { columns, filters, sorters, pinning } = currentGridState; + const { columns, filters, sorters, pinning } = this.sgb.gridStateService.getCurrentGridState(); if (this.currentSelectedViewPreset && filters) { const filterName = await prompt(`Update View name or click on OK to continue.`, this.currentSelectedViewPreset.label); @@ -830,7 +837,11 @@ export default class Example11 { this.sgb.gridService.clearPinning(); this.sgb.filterService.clearFilters(); this.sgb.sortService.clearSorting(); - this.sgb.gridStateService.changeColumnsArrangement([...this.columnDefinitions].map((col) => ({ columnId: `${col.id}` }))); + this.sgb.gridStateService.changeColumnsArrangement( + [...this.columnDefinitions].map((col) => ({ columnId: `${col.id}` })) + // OR the `hidden` props alternative + // [...this.columnDefinitions].map((col) => ({ columnId: `${col.id}`, hidden: false })) + ); } localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.predefinedViews)); this.currentSelectedViewPreset = selectedView; diff --git a/demos/vanilla/src/examples/example12.ts b/demos/vanilla/src/examples/example12.ts index e78f5a0127..d9a0bfb4dd 100644 --- a/demos/vanilla/src/examples/example12.ts +++ b/demos/vanilla/src/examples/example12.ts @@ -1,7 +1,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Editors, - EventNamingStyle, Filters, formatNumber, Formatters, @@ -500,7 +499,7 @@ export default class Example12 { autoFixResizeRequiredGoodCount: 1, datasetIdPropertyName: 'id', darkMode: this._darkMode, - eventNamingStyle: EventNamingStyle.lowerCase, + eventNamingStyle: 'lowerCase', autoAddCustomEditorFormatter: customEditableInputFormatter, enableAddRow: true, // <-- this flag is required to work with the (create & clone) modal types enableCellNavigation: true, @@ -525,7 +524,7 @@ export default class Example12 { }, externalResources: [new ExcelExportService(), this.compositeEditorInstance], enableFiltering: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -535,7 +534,7 @@ export default class Example12 { rowHeight: 33, headerRowHeight: 35, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { applySelectOnAllPages: true, hideInFilterHeaderRow: false, @@ -1082,6 +1081,7 @@ export default class Example12 { setTimeout(() => { this.compositeEditorInstance?.openDetails({ + // domElementType: 'div', // use as fallback when default doesn't work (like in Salesforce LWC), accepts 'div' or 'dialog' headerTitle: modalTitle, modalType, insertOptions: { highlightRow: false }, // disable highlight to avoid flaky tests in Cypress diff --git a/demos/vanilla/src/examples/example14.ts b/demos/vanilla/src/examples/example14.ts index 542a72fd72..b0a25f5452 100644 --- a/demos/vanilla/src/examples/example14.ts +++ b/demos/vanilla/src/examples/example14.ts @@ -1,7 +1,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Editors, - EventNamingStyle, Filters, // utilities formatNumber, @@ -544,7 +543,7 @@ export default class Example14 { }); this.gridOptions = { - eventNamingStyle: EventNamingStyle.lowerCase, + eventNamingStyle: 'lowerCase', editable: true, autoAddCustomEditorFormatter: customEditableInputFormatter, enableCellNavigation: true, @@ -584,14 +583,14 @@ export default class Example14 { }, externalResources: [new SlickCustomTooltip(), new ExcelExportService()], enableFiltering: true, - enableRowSelection: true, + enableSelection: true, enableCheckboxSelector: true, checkboxSelector: { applySelectOnAllPages: true, // already defaults to true hideInFilterHeaderRow: false, hideInColumnTitleRow: true, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, @@ -763,7 +762,7 @@ export default class Example14 { // just for demo purposes, set it back to its original width const columns = this.sgb.slickGrid?.getColumns() as Column[]; columns.forEach((col) => (col.width = col.originalWidth)); - this.sgb.slickGrid?.setColumns(columns); + this.sgb.slickGrid?.updateColumns(); this.sgb.slickGrid?.autosizeColumns(); // simple css class to change selected button in the UI diff --git a/demos/vanilla/src/examples/example15.ts b/demos/vanilla/src/examples/example15.ts index 28adf6f52e..0de56b47e2 100644 --- a/demos/vanilla/src/examples/example15.ts +++ b/demos/vanilla/src/examples/example15.ts @@ -1,13 +1,5 @@ import { BindingEventService } from '@slickgrid-universal/binding'; -import { - Editors, - Filters, - OperatorType, - type Column, - type GridOption, - type GridStateChange, - type Metrics, -} from '@slickgrid-universal/common'; +import { Editors, Filters, type Column, type GridOption, type GridStateChange, type Metrics } from '@slickgrid-universal/common'; import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { GridOdataService, type OdataOption, type OdataServiceApi } from '@slickgrid-universal/odata'; import { RxJsResource } from '@slickgrid-universal/rxjs-observable'; @@ -155,7 +147,7 @@ export default class Example15 { enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enablePagination: true, // you could optionally disable the Pagination pagination: { pageSizes: [10, 20, 50, 100, 500, 50000], @@ -164,8 +156,8 @@ export default class Example15 { presets: { // you can also type operator as string, e.g.: operator: 'EQ' filters: [ - // { columnId: 'name', searchTerms: ['w'], operator: OperatorType.startsWith }, - { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'name', searchTerms: ['w'], operator: 'StartsWith' }, + { columnId: 'gender', searchTerms: ['male'], operator: '=' }, ], sorters: [ // direction can be written as 'asc' (uppercase or lowercase) and/or use the SortDirection type @@ -454,7 +446,7 @@ export default class Example15 { setFiltersDynamically() { // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.sgb?.filterService.updateFilters([ - // { columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal }, + // { columnId: 'gender', searchTerms: ['male'], operator: '=' }, { columnId: 'name', searchTerms: ['A'], operator: 'a*' }, ]); } diff --git a/demos/vanilla/src/examples/example16.html b/demos/vanilla/src/examples/example16.html index a8451d2923..4ed1896305 100644 --- a/demos/vanilla/src/examples/example16.html +++ b/demos/vanilla/src/examples/example16.html @@ -2,7 +2,12 @@

    Example 16 - Regular & Custom Tooltips (with Salesforce Theme) - @@ -19,17 +24,29 @@

  • -
    +
    - -
    -
    Grid 1 - Using SlickCellRangeSelector
    +
    + Grid 1 - Using SlickCellRangeSelector with SlickHybridRangeSelector({ selectionType: 'cell' }) +

    -
    Grid 2 - Using SlickCellRangeSelector and SlickRowSelectionModel
    +
    + Grid 2 - Using SlickCellRangeSelector and SlickHybridRangeSelector({ selectionType: 'row' }) +
    diff --git a/demos/vanilla/src/examples/example17.ts b/demos/vanilla/src/examples/example17.ts index e3c83b2df8..a133745b42 100644 --- a/demos/vanilla/src/examples/example17.ts +++ b/demos/vanilla/src/examples/example17.ts @@ -3,8 +3,7 @@ import { Formatters, GroupTotalFormatters, SlickCellRangeSelector, - SlickCellSelectionModel, - SlickRowSelectionModel, + SlickHybridSelectionModel, type Column, type GridOption, type Grouping, @@ -233,7 +232,8 @@ export default class Example17 { setOptions() { this.sgb1.slickGrid?.setSelectionModel( - new SlickCellSelectionModel({ + new SlickHybridSelectionModel({ + selectionType: 'cell', selectActiveCell: true, cellRangeSelector: new SlickCellRangeSelector({ selectionCss: { @@ -248,8 +248,8 @@ export default class Example17 { ); this.sgb2.slickGrid?.setSelectionModel( - // or use SlickHybridSelectionModel with `selectionType: 'row'` - new SlickRowSelectionModel({ + new SlickHybridSelectionModel({ + selectionType: 'row', cellRangeSelector: new SlickCellRangeSelector({ selectionCss: { border: 'none', diff --git a/demos/vanilla/src/examples/example19.html b/demos/vanilla/src/examples/example19.html index 1c68629d34..bd6033355f 100644 --- a/demos/vanilla/src/examples/example19.html +++ b/demos/vanilla/src/examples/example19.html @@ -20,7 +20,7 @@

    -

    Grid - using enableExcelCopyBuffer which uses SlickCellSelectionModel

    +

    Grid - using enableExcelCopyBuffer which uses SlickHybridSelectionModel

    The complete first row and the cells C - E of the second row are not allowing to paste values.

    Additionally the columns are configured to exportWithFormatter and a custom formatter that renders the cells coordinates or diff --git a/demos/vanilla/src/examples/example20.ts b/demos/vanilla/src/examples/example20.ts index 992e6d04be..1a72a4b4dc 100644 --- a/demos/vanilla/src/examples/example20.ts +++ b/demos/vanilla/src/examples/example20.ts @@ -2,7 +2,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { createDomElement, Editors, - ExtensionName, Filters, Formatters, SlickEventHandler, @@ -177,7 +176,7 @@ export default class Example20 { // Row Detail View is a special case because of its requirement to create extra column definition dynamically // so it must be pre-registered before SlickGrid is instantiated, we can do so via this option this.rowDetail = new SlickRowDetailView(pubSubService); - return [{ name: ExtensionName.rowDetailView, instance: this.rowDetail }]; + return [{ name: 'rowDetailView', instance: this.rowDetail }]; }, rowHeight: 33, rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" @@ -198,14 +197,14 @@ export default class Example20 { // you can override it here in the options or externally by calling the method on the plugin instance expandableOverride: (_row, dataContext) => dataContext.id % 2 === 1, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, // You could also enable Row Selection as well, but just make sure to disable `useRowClick: false` enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideSelectAllCheckbox: true, diff --git a/demos/vanilla/src/examples/example21.ts b/demos/vanilla/src/examples/example21.ts index 6535a53deb..71a4581a49 100644 --- a/demos/vanilla/src/examples/example21.ts +++ b/demos/vanilla/src/examples/example21.ts @@ -1,6 +1,6 @@ import { faker } from '@faker-js/faker'; import { BindingEventService } from '@slickgrid-universal/binding'; -import { createDomElement, ExtensionName, SlickEventHandler, type Column, type GridOption } from '@slickgrid-universal/common'; +import { createDomElement, SlickEventHandler, type Column, type GridOption } from '@slickgrid-universal/common'; import { SlickRowDetailView } from '@slickgrid-universal/row-detail-view-plugin'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { ExampleGridOptions } from './example-grid-options.js'; @@ -140,7 +140,7 @@ export default class Example21 { // Row Detail View is a special case because of its requirement to create extra column definition dynamically // so it must be pre-registered before SlickGrid is instantiated, we can do so via this option this.rowDetail = new SlickRowDetailView(pubSubService); - return [{ name: ExtensionName.rowDetailView, instance: this.rowDetail }]; + return [{ name: 'rowDetailView', instance: this.rowDetail }]; }, rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" rowHeight: 33, diff --git a/demos/vanilla/src/examples/example25.ts b/demos/vanilla/src/examples/example25.ts index 77aab02cbe..19a3f4abc5 100644 --- a/demos/vanilla/src/examples/example25.ts +++ b/demos/vanilla/src/examples/example25.ts @@ -3,7 +3,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Filters, Formatters, - OperatorType, type Column, type CurrentFilter, type GridOption, @@ -124,7 +123,7 @@ export default class Example25 { filter: { model: Filters.sliderRange, maxValue: 100, // or you can use the options as well - operator: OperatorType.rangeInclusive, // defaults to inclusive + operator: 'RangeInclusive', // defaults to inclusive options: { hideSliderNumbers: false, // you can hide/show the slider numbers on both side min: 0, @@ -173,7 +172,7 @@ export default class Example25 { filterable: true, filter: { model: Filters.input, - operator: OperatorType.rangeExclusive, // defaults to exclusive + operator: 'RangeExclusive', // defaults to exclusive }, }, { @@ -313,10 +312,10 @@ export default class Example25 { filters = [ { columnId: 'finish', - operator: OperatorType.rangeInclusive, + operator: 'RangeInclusive', searchTerms: [`${currentYear}-01-01`, `${currentYear}-12-31`], }, - { columnId: 'completed', operator: OperatorType.equal, searchTerms: [true] }, + { columnId: 'completed', operator: '=', searchTerms: [true] }, ]; break; case 'nextYearTasks': diff --git a/demos/vanilla/src/examples/example26.ts b/demos/vanilla/src/examples/example26.ts index 566e7013e9..12be05f140 100644 --- a/demos/vanilla/src/examples/example26.ts +++ b/demos/vanilla/src/examples/example26.ts @@ -3,7 +3,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Aggregators, Filters, - OperatorType, SortComparers, type Column, type GridOption, @@ -120,7 +119,7 @@ export default class Example26 { enableCellNavigation: true, enableFiltering: true, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, enableGrouping: true, headerMenu: { hideFreezeColumnsCommand: false, @@ -137,7 +136,7 @@ export default class Example26 { enableCount: true, filterQueryOverride: ({ fieldName, columnDef, columnFilterOperator, searchValues }) => { - if (columnFilterOperator === OperatorType.custom && columnDef?.id === 'name') { + if (columnFilterOperator === 'Custom' && columnDef?.id === 'name') { let matchesSearch = searchValues[0].replace(/\*/g, '.*'); matchesSearch = matchesSearch.slice(0, 1) + CARET_HTML_ESCAPED + matchesSearch.slice(1); matchesSearch = matchesSearch.slice(0, -1) + "$'"; diff --git a/demos/vanilla/src/examples/example29.ts b/demos/vanilla/src/examples/example29.ts index afa8542d58..d8a883a433 100644 --- a/demos/vanilla/src/examples/example29.ts +++ b/demos/vanilla/src/examples/example29.ts @@ -76,11 +76,12 @@ export default class Example29 { gridWidth: 800, rowHeight: 33, enableCellNavigation: true, - enableRowSelection: true, + enableSelection: true, enableRowMoveManager: true, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, + selectionType: 'row', }, rowMoveManager: { columnIndexPosition: 0, diff --git a/demos/vanilla/src/examples/example30.ts b/demos/vanilla/src/examples/example30.ts index a24540570f..23b7b7f4d5 100644 --- a/demos/vanilla/src/examples/example30.ts +++ b/demos/vanilla/src/examples/example30.ts @@ -1,7 +1,6 @@ import { Filters, Formatters, - OperatorType, type Column, type GridOption, type MultipleSelectOption, @@ -80,7 +79,7 @@ export default class Example30 { filter: { model: Filters.sliderRange, maxValue: 100, // or you can use the options as well - operator: OperatorType.rangeInclusive, // defaults to inclusive + operator: 'RangeInclusive', // defaults to inclusive options: { hideSliderNumbers: false, // you can hide/show the slider numbers on both side min: 0, @@ -126,7 +125,7 @@ export default class Example30 { filterable: true, filter: { model: Filters.input, - operator: OperatorType.rangeExclusive, // defaults to exclusive + operator: 'RangeExclusive', // defaults to exclusive }, }, { diff --git a/demos/vanilla/src/examples/example32.ts b/demos/vanilla/src/examples/example32.ts index c25474e8a6..8b35a7affd 100644 --- a/demos/vanilla/src/examples/example32.ts +++ b/demos/vanilla/src/examples/example32.ts @@ -458,7 +458,8 @@ export default class Example32 { } } - // update column definitions + // 1. update column definitions via grid.setColumns() + // this will shift colspan/rowspan to the left or right accordingly if (this.showEmployeeId) { this.columnDefinitions.unshift({ id: 'employeeID', name: 'Employee ID', field: 'employeeID', width: 100 }); } else { @@ -466,6 +467,22 @@ export default class Example32 { } this.sgb.slickGrid?.setColumns(this.columnDefinitions); + // --- OR --- + // 2. OR update via "hidden" column flag & increase/decrease column index accordingly in the metadata + // this approach will keep colspan/rowspan "as-is" but will hide the EmployeeID column + /* + const colDirIdx = this.showEmployeeId ? -1 : 1; + for (const row of Object.keys(this.metadata)) { + newMetadata[row] = { columns: {} }; + for (const col of Object.keys((this.metadata as any)[row].columns)) { + newMetadata[row].columns[Number(col) + colDirIdx] = (this.metadata as any)[row].columns[col]; + } + } + this.sgb.slickGrid?.setOptions({ frozenColumn: this.showEmployeeId ? 0 : 1 }); + this.sgb.slickGrid?.updateColumnById('employeeID', { hidden: !this.showEmployeeId }); + this.sgb.slickGrid?.updateColumns(); + */ + // update & remap rowspans this.metadata = newMetadata; this.sgb.slickGrid?.remapAllColumnsRowSpan(); diff --git a/demos/vanilla/src/examples/example34.ts b/demos/vanilla/src/examples/example34.ts index ec6d0afd35..b37a32e23c 100644 --- a/demos/vanilla/src/examples/example34.ts +++ b/demos/vanilla/src/examples/example34.ts @@ -134,8 +134,8 @@ export default class Example34 { enableExcelExport: true, externalResources: [new ExcelExportService()], enableCheckboxSelector: true, - enableRowSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, diff --git a/demos/vanilla/src/examples/example35.ts b/demos/vanilla/src/examples/example35.ts index bc96449db7..e0ea2b157d 100644 --- a/demos/vanilla/src/examples/example35.ts +++ b/demos/vanilla/src/examples/example35.ts @@ -124,7 +124,7 @@ export default class Example35 { sanitizeDataExport: true, }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, multiSelect: false, checkboxSelector: { // columnIndexPosition: 1, diff --git a/demos/vanilla/src/examples/example36.ts b/demos/vanilla/src/examples/example36.ts index 10d944338a..691e68ee44 100644 --- a/demos/vanilla/src/examples/example36.ts +++ b/demos/vanilla/src/examples/example36.ts @@ -2,7 +2,6 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Aggregators, createDomElement, - ExtensionName, Filters, Formatters, GroupTotalFormatters, @@ -199,7 +198,7 @@ export default class Example36 { // Row Detail View is a special case because of its requirement to create extra column definition dynamically // so it must be pre-registered before SlickGrid is instantiated, we can do so via this option this.rowDetail = new SlickRowDetailView(pubSubService); - return [{ name: ExtensionName.rowDetailView, instance: this.rowDetail }]; + return [{ name: 'rowDetailView', instance: this.rowDetail }]; }, rowHeight: 33, rowTopOffsetRenderType: 'top', // RowDetail and/or RowSpan don't render well with "transform", you should use "top" @@ -216,14 +215,14 @@ export default class Example36 { // example, if you choosed 6 panelRows, the display will in fact use 5 rows panelRows: this.detailViewRowCount, }, - rowSelectionOptions: { + selectionOptions: { // True (Single Selection), False (Multiple Selections) selectActiveRow: false, }, // You could also enable Row Selection as well, but just make sure to disable `useRowClick: false` enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideSelectAllCheckbox: true, diff --git a/demos/vanilla/src/examples/example37.html b/demos/vanilla/src/examples/example37.html index 75f7407042..e7aaafb3af 100644 --- a/demos/vanilla/src/examples/example37.html +++ b/demos/vanilla/src/examples/example37.html @@ -22,8 +22,9 @@