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 8a603c172f..a656074633 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -25,13 +25,6 @@ enabled: false, }, }, - { - description: 'Prevent major updates for Vite 6 catalog', - matchDepTypes: ['pnpm.catalog.vite6'], - major: { - enabled: false, - }, - }, ], 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 109e1e4212..fdedfc9c1d 100644 --- a/.github/workflows/test-angular.yml +++ b/.github/workflows/test-angular.yml @@ -70,17 +70,12 @@ 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 - - name: Cache Cypress binary - uses: actions/cache@v5 - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-cypress- - - name: Ensure Cypress binary is installed run: pnpm exec cypress install @@ -88,9 +83,10 @@ 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 working-directory: frameworks/angular-slickgrid # start: pnpm angular:serve wait-on: 'http://localhost:4300' diff --git a/.github/workflows/test-aurelia.yml b/.github/workflows/test-aurelia.yml index 38af7278dc..f8d83ee847 100644 --- a/.github/workflows/test-aurelia.yml +++ b/.github/workflows/test-aurelia.yml @@ -55,17 +55,12 @@ 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 - - name: Cache Cypress binary - uses: actions/cache@v5 - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-cypress- - - name: Ensure Cypress binary is installed run: pnpm exec cypress install @@ -73,9 +68,10 @@ 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 working-directory: demos/aurelia # start: pnpm aurelia:serve wait-on: 'http://localhost:7900' diff --git a/.github/workflows/test-react.yml b/.github/workflows/test-react.yml index a7d57754ca..3838b22532 100644 --- a/.github/workflows/test-react.yml +++ b/.github/workflows/test-react.yml @@ -55,17 +55,12 @@ 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 - - name: Cache Cypress binary - uses: actions/cache@v5 - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-cypress- - - name: Ensure Cypress binary is installed run: pnpm exec cypress install @@ -73,9 +68,10 @@ 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 working-directory: demos/react wait-on: 'http://localhost:8000' config-file: test/cypress.config.ts diff --git a/.github/workflows/test-vanilla.yml b/.github/workflows/test-vanilla.yml index 460e53571e..4cb1fa2d2f 100644 --- a/.github/workflows/test-vanilla.yml +++ b/.github/workflows/test-vanilla.yml @@ -55,14 +55,6 @@ jobs: - name: Website Dev Build (served for Cypress) run: pnpm build:dev - - name: Cache Cypress binary - uses: actions/cache@v5 - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-cypress- - - name: Ensure Cypress binary is installed run: pnpm exec cypress install @@ -70,9 +62,10 @@ 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 # working-directory: packages/dnd # start: pnpm vanilla:serve:demo # start: pnpm serve:vite diff --git a/.github/workflows/test-vue.yml b/.github/workflows/test-vue.yml index e276f1cda3..237b038b44 100644 --- a/.github/workflows/test-vue.yml +++ b/.github/workflows/test-vue.yml @@ -55,17 +55,12 @@ 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 - - name: Cache Cypress binary - uses: actions/cache@v5 - with: - path: ~/.cache/Cypress - key: ${{ runner.os }}-cypress-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-cypress- - - name: Ensure Cypress binary is installed run: pnpm exec cypress install @@ -73,9 +68,10 @@ 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 working-directory: demos/vue # start: pnpm vue:serve wait-on: 'http://localhost:7000' 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 8e9af0ef50..a9b3aac12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,80 @@ 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 + +* add minus icon for partial row selection ([#2322](https://github.com/ghiscoding/slickgrid-universal/issues/2322)) ([d3dc6e7](https://github.com/ghiscoding/slickgrid-universal/commit/d3dc6e7772243a3d0dd6961680698d05a834ae58)) - by @ghiscoding +* deprecate `rowSelectionOptions` and rename to `selectionOptions` ([#2325](https://github.com/ghiscoding/slickgrid-universal/issues/2325)) ([ec4d546](https://github.com/ghiscoding/slickgrid-universal/commit/ec4d546b3e0c76edcd12899b16a1f9403550cf0c)) - by @ghiscoding +* **export:** add new optional PDF Export package ([#2317](https://github.com/ghiscoding/slickgrid-universal/issues/2317)) ([42347f6](https://github.com/ghiscoding/slickgrid-universal/commit/42347f605388d3c080568d8bbe93dcf7c5ed1ff4)) - by @ghiscoding +* update to latest Aurelia 2 RC.0 ([#2315](https://github.com/ghiscoding/slickgrid-universal/issues/2315)) ([4ea46a6](https://github.com/ghiscoding/slickgrid-universal/commit/4ea46a619e214f5272c364d57d51336c37845035)) - by @ghiscoding + +### Bug Fixes + +* allow single row selection via click without checkbox column ([#2311](https://github.com/ghiscoding/slickgrid-universal/issues/2311)) ([b2c6594](https://github.com/ghiscoding/slickgrid-universal/commit/b2c659445c5c39b2b76613c113cef833bdd99f97)) - by @ghiscoding +* **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 +* **deps:** update all non-major dependencies ([#2320](https://github.com/ghiscoding/slickgrid-universal/issues/2320)) ([9d9f7ad](https://github.com/ghiscoding/slickgrid-universal/commit/9d9f7ad9c0d2b7b3ee768b8061dfd646dc75bb81)) - by @renovate-bot +* **deps:** update all non-major dependencies ([#2342](https://github.com/ghiscoding/slickgrid-universal/issues/2342)) ([3b2a921](https://github.com/ghiscoding/slickgrid-universal/commit/3b2a921ff5cae8de5da78f36d7a625fa79eeb557)) - by @renovate-bot +* double-clicking a checkmark icon should keep current cell selection ([#2312](https://github.com/ghiscoding/slickgrid-universal/issues/2312)) ([1046d2f](https://github.com/ghiscoding/slickgrid-universal/commit/1046d2f6451994ab8eeda7bb87e80297ac038247)) - by @ghiscoding +* Excel/Text Exports display top/preheader titles w/DraggableGrouping ([#2314](https://github.com/ghiscoding/slickgrid-universal/issues/2314)) ([28817e8](https://github.com/ghiscoding/slickgrid-universal/commit/28817e8055b81ee55c1b3bac17cd7514a3921ff0)) - 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 +* only assign CellRangeSelector when Hybrid has `dragToSelect` set ([#2329](https://github.com/ghiscoding/slickgrid-universal/issues/2329)) ([a941819](https://github.com/ghiscoding/slickgrid-universal/commit/a941819649dfa1b54f7887603b930cab27d67c3a)) - by @ghiscoding +* should stop Draggable service when setting hybrid 'row-click' ([#2313](https://github.com/ghiscoding/slickgrid-universal/issues/2313)) ([0082b2d](https://github.com/ghiscoding/slickgrid-universal/commit/0082b2d532223f03854f9c0a8766e6f2971f3333)) - by @ghiscoding +* this supports now multiple edge cases when copy pasting from excel to grid ([#2340](https://github.com/ghiscoding/slickgrid-universal/issues/2340)) ([2b4e2ba](https://github.com/ghiscoding/slickgrid-universal/commit/2b4e2baded436a89b9780563485af92bd60ea706)) - by @zewa666 + ## [9.12.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.11.0...v9.12.0) (2025-12-29) ### Features diff --git a/README.md b/README.md index 42065a2f0d..9ce91a785f 100644 --- a/README.md +++ b/README.md @@ -15,42 +15,44 @@ 📘 [Documentation](https://ghiscoding.gitbook.io/slickgrid-universal/) website powered by GitBook for version 4.0+ (_or use the [Wikis](https://github.com/ghiscoding/slickgrid-universal/wiki) for older versions_) ### Live Demo -[Live Demo](https://ghiscoding.github.io/slickgrid-universal/) website +[Live Demo](https://ghiscoding.github.io/slickgrid-universal/) website OR see [Available Framework Wrappers](#available-framework-wrappers) table below for more demos. -Also available in Stackblitz for all available frameworks (see [table](https://github.com/ghiscoding/slickgrid-universal?tab=readme-ov-file#available-framework-wrappers) below) +Stackblitz links are also available for all frameworks supported (see [table](https://github.com/ghiscoding/slickgrid-universal?tab=readme-ov-file#available-framework-wrappers) below) ### Description One of the best JavaScript data grid named "SlickGrid", which was originally developed by @mleibman, beats most other data grids in terms of features, customizability & performance (running smoothly with even a million rows). -This is a monorepo project (using [pnpm workspaces](https://pnpm.io/workspaces) and [Lerna-Lite](https://github.com/lerna-lite/lerna-lite)) which is regrouping a few packages under a single repository. It was originally requiring `@6pac/SlickGrid` as an external dependency, but that was dropped in v4.0, and it has been a standalone library ever since. The main goal of this project is to create a common repo that includes all Editors, Filters, Extensions and Services that could be used by any frameworks (the project is framework agnostic). +This is a monorepo project (using [pnpm workspaces](https://pnpm.io/workspaces) and [Lerna-Lite](https://github.com/lerna-lite/lerna-lite)) which is regrouping a few packages under a single repository. It was originally requiring `@6pac/SlickGrid` as an external dependency, but that was dropped in v4.0, and it has been a standalone library ever since. The main goal of this project is to create a common repo that includes all Editors, Filters, Extensions and Services that could be used by any frameworks (the core project in itself is framework agnostic). ### What's the difference with the original SlickGrid (now [`@6pac/SlickGrid`](https://github.com/6pac/SlickGrid)) project? If you've used the original SlickGrid in the past, you might be wondering, should I use the [`@6pac/SlickGrid`](https://github.com/6pac/SlickGrid) or Slickgrid-Universal? The main difference is that the original SlickGrid is like an unassembled IKEA product where it's very bare bone and unassembled, but on the other hand Slickgrid-Universal is an all assembled product with batteries included. What that means is that Slickgrid-Universal comes with many built-in features like Formatters, Editors, Filters, Tree Data, ... which are not directly available in the original SlickGrid. So in the end SlickGrid (`@6pac/SlickGrid`) project is much smaller in size because it's very bare bone but you will have to implement many things yourself (Sorting/Filtering/Editing/...), and if you're looking at creating very basic grids with the smallest footprint possible, then SlickGrid might just work for you, otherwise Slickgrid-Universal has a more complete set of features out of the box with a bit larger download and installation size. -Side note, I am (`@ghiscoding`) a maintainer in both projects, which are Slickgrid-Universal as well as the [`@6pac/SlickGrid`](https://github.com/6pac/SlickGrid) (in fact Slickgrid-Universal was originally requiring the `@6pac/SlickGrid` dependency but that requirement was eventually dropped in v4 and it is now a standalone). The main reason to support both projects is to keep core files in sync as much as possible (SlickGrid, SlickDataView and all plugins). When combined together, both projects have a much larger user base and this mean much more stability, and I also often sync new core & plugin files in both projects as well... and that's it, I hope this makes it clear what the differences are. +Side note, I am (`@ghiscoding`) a maintainer in both projects (Slickgrid-Universal and [`@6pac/SlickGrid`](https://github.com/6pac/SlickGrid)), in fact Slickgrid-Universal was originally requiring the `@6pac/SlickGrid` dependency but that requirement was dropped in v4 and is now a standalone. The main reason to support both projects is to keep core files in sync as much as possible (SlickGrid, SlickDataView and all plugins). Combined together, both projects have a much larger user base and this mean much more stability, and I also often sync new core & plugin files in both projects as well... and that's it, I hope this makes it clear what the differences are. ### Why create this monorepo? Below is a list of reasons as to why this project was created and why it is built as a monorepo project: 1. avoids duplicate code by creating common packages available in Slickgrid-Universal and used by all frameworks 2. many of the Services are decoupled because most project will never require all of these services all at once - - OData, GraphQL, Export to CSV, Export to Excel, Composite Editor, RxJS, ... + - OData, GraphQL, Export to (CSV, Excel or PDF), Composite Editor, RxJS, ... 4. and finally it is framework agnostic - - you can reuse the same grids and logic in many different frameworks, because it's easily transportable - - you can use it in plain JavaScript (ES6) or TypeScript, e.g. we use it ourselves as plain JS (ES6) in our Salesforce environment with LWC (Lightning Web Component) + - you can reuse the same grids and logic in many different frameworks, it's easily transportable + - you can use it in plain JavaScript (ES6) or TypeScript, for example we use it ourselves as plain JS (ES6) in our Salesforce environment with LWC (Lightning Web Component) ## Latest News & Releases Check out the [Releases](https://github.com/ghiscoding/slickgrid-universal/releases) section for all the latest News & Releases. ### Like my work? -You could star ⭐ the project and/or support me with caffeine via GitHub [sponsorship](https://github.com/sponsors/ghiscoding) or the Ko-Fi button below. Thanks in advance. +If you use and like the project, please give it a star ⭐ and/or support me with caffeine via GitHub [sponsorship](https://github.com/sponsors/ghiscoding) or the Ko-Fi button below would be much appreciated. Thanks in advance. Buy Me a Coffee at ko-fi.com ## Live Demos & Available Framework Wrappers -The Slickgrid-Universal [live demo](https://ghiscoding.github.io/slickgrid-universal) shows 2 different UI themes (Material Design / Salesforce) and you can also see the Bootstrap theme demoed in all other frameworks with links available in the table shown below. Also please note that even if all the demos are built with [Bootstrap](https://getbootstrap.com/) or [Bulma](https://bulma.io/), you could in theory use any other UI libraries (e.g. Google Material, ...). The project tries to be as much agnostic as possible and it does so by providing a large set of CSS/SASS variables which are available to customize the UI however you want. +The Slickgrid-Universal [live demo](https://ghiscoding.github.io/slickgrid-universal) shows 2 different UI themes (Material Design / Salesforce) and you can also see the Bootstrap theme demoed in all other frameworks with links available in the table shown below. Also please note that even if all the demos are built with [Bootstrap](https://getbootstrap.com/) or [Bulma](https://bulma.io/), you could use any other UI libraries (Google Material, ...). The project tries to be as much agnostic as possible and it does so by providing a large set of CSS/SASS variables which are available to customize the UI however you want. ### Available Framework Wrappers +> There's no SolidJS or Svelte wrappers, but if you are willing to help then let's talk and open a new [Discussion](https://github.com/ghiscoding/slickgrid-universal/discussions) + | Platform | Project Repos | Live Demo | Stackblitz | Downloads | Details | | :------: | ------------- | --------- | ---------- | -------: | ------- | | Angular | [Angular-Slickgrid](https://github.com/ghiscoding/slickgrid-universal/tree/master/frameworks/angular-slickgrid) /
[Angular-Slickgrid-Demos](https://github.com/ghiscoding/angular-slickgrid-demos) | [demo](https://ghiscoding.github.io/angular-slickgrid-demos) | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/ghiscoding/angular-slickgrid-demos) | [![NPM downloads](https://img.shields.io/npm/dy/angular-slickgrid)](https://npmjs.org/package/angular-slickgrid)
[![npm bundle size](https://img.shields.io/bundlephobia/minzip/angular-slickgrid?color=success&label=gzip)](https://bundlephobia.com/result?p=angular-slickgrid) | [docs](https://ghiscoding.gitbook.io/angular-slickgrid/getting-started/quick-start) /
[changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/frameworks/angular-slickgrid/CHANGELOG.md) | @@ -62,7 +64,7 @@ The Slickgrid-Universal [live demo](https://ghiscoding.github.io/slickgrid-unive | Salesforce (LWC) | [Slickgrid-Universal/vanilla-force-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-force-bundle) | n/a | | [zip](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-force-bundle/dist-grid-bundle-zip) file | [docs](https://ghiscoding.gitbook.io/slickgrid-universal/getting-started/installation-salesforce) | | Streamlit | [streamlit-slickgrid](https://github.com/streamlit/streamlit-slickgrid/) | [demo](https://slickgrid.streamlit.app/) | | | Python wrapper - community driven | -The Slickgrid-Universal [live demo](https://ghiscoding.github.io/slickgrid-universal) is a Vanilla Implementation (which is not associated to any framework) built with [ViteJS](https://vitejs.dev/) (originally [WebPack](https://webpack.js.org/)) and is also being used to run all E2E tests with [Cypress](https://www.cypress.io/) for testing every UI functionalities. The [Vanilla-force-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-force-bundle), which extends the [vanilla-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-bundle) package, is what we use in our SalesForce LWC (Lightning Web Component) implementation and this Zip file can also be used as a Standalone script (see [zip](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-force-bundle/dist-grid-bundle-zip) file). These were all the original reasons to create the library under a monorepo structure, but above all, it was to avoid code duplication. +The Slickgrid-Universal [live demo](https://ghiscoding.github.io/slickgrid-universal) is a Vanilla Implementation (which is not associated to any framework) built with [ViteJS](https://vitejs.dev/) (originally [WebPack](https://webpack.js.org/)) and is also being used to run all E2E tests with [Cypress](https://www.cypress.io/) for testing every UI functionalities. The [Vanilla-force-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-force-bundle), which extends the [vanilla-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-bundle) package, is what we use in our SalesForce LWC (Lightning Web Component) implementation and this Zip file can also be used as a Standalone script (see [zip](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-force-bundle/dist-grid-bundle-zip) file). These were all the original reasons to create the library under a monorepo structure, but above all, it was mainly to avoid code duplication. Dark Mode is also shown in some of the examples (not all), see [Dark Mode](https://ghiscoding.gitbook.io/slickgrid-universal/styling/dark-mode) documentation for more infos. @@ -82,6 +84,7 @@ Slickgrid-Universal has **100%** Unit Test Coverage, 5000+ Vitest unit tests and | [@slickgrid-universal/empty-warning-component](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/empty-warning-component) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/empty-warning-component.svg)](https://www.npmjs.com/package/@slickgrid-universal/empty-warning-component) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/empty-warning-component.svg)](https://www.npmjs.com/package/@slickgrid-universal/empty-warning-component) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/empty-warning-component?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/empty-warning-component) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/empty-warning-component/CHANGELOG.md) | | [@slickgrid-universal/pagination-component](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/pagination-component) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/pagination-component.svg)](https://www.npmjs.com/package/@slickgrid-universal/pagination-component) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/pagination-component.svg)](https://www.npmjs.com/package/@slickgrid-universal/pagination-component) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/pagination-component?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/pagination-component) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/pagination-component/CHANGELOG.md) | | [@slickgrid-universal/excel-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/excel-export) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/excel-export.svg)](https://www.npmjs.com/package/@slickgrid-universal/excel-export) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/excel-export.svg)](https://www.npmjs.com/package/@slickgrid-universal/excel-export) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/excel-export?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/excel-export) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/excel-export/CHANGELOG.md) | +| [@slickgrid-universal/pdf-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/pdf-export) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/pdf-export.svg)](https://www.npmjs.com/package/@slickgrid-universal/pdf-export) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/pdf-export.svg)](https://www.npmjs.com/package/@slickgrid-universal/pdf-export) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/pdf-export?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/pdf-export) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/pdf-export/CHANGELOG.md) | | [@slickgrid-universal/text-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/text-export) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/text-export.svg)](https://www.npmjs.com/package/@slickgrid-universal/text-export) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/text-export.svg)](https://www.npmjs.com/package/@slickgrid-universal/text-export) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/text-export?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/text-export) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/text-export/CHANGELOG.md) | | [@slickgrid-universal/graphql](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/graphql) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/graphql.svg)](https://www.npmjs.com/package/@slickgrid-universal/graphql) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/graphql.svg)](https://www.npmjs.com/package/@slickgrid-universal/graphql) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/graphql?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/graphql) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/graphql/CHANGELOG.md) | | [@slickgrid-universal/odata](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/odata) | [![npm](https://img.shields.io/npm/v/@slickgrid-universal/odata.svg)](https://www.npmjs.com/package/@slickgrid-universal/odata) | [![NPM downloads](https://img.shields.io/npm/dy/@slickgrid-universal/odata.svg)](https://www.npmjs.com/package/@slickgrid-universal/odata) | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@slickgrid-universal/odata?color=success&label=gzip)](https://bundlephobia.com/result?p=@slickgrid-universal/odata) | [changelog](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/odata/CHANGELOG.md) | diff --git a/demos/aurelia/CHANGELOG.md b/demos/aurelia/CHANGELOG.md index 717e807a95..daa98ab2f1 100644 --- a/demos/aurelia/CHANGELOG.md +++ b/demos/aurelia/CHANGELOG.md @@ -4,6 +4,50 @@ 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 + +* **export:** add new optional PDF Export package ([#2317](https://github.com/ghiscoding/slickgrid-universal/issues/2317)) ([42347f6](https://github.com/ghiscoding/slickgrid-universal/commit/42347f605388d3c080568d8bbe93dcf7c5ed1ff4)) - by @ghiscoding +* update to latest Aurelia 2 RC.0 ([#2315](https://github.com/ghiscoding/slickgrid-universal/issues/2315)) ([4ea46a6](https://github.com/ghiscoding/slickgrid-universal/commit/4ea46a619e214f5272c364d57d51336c37845035)) - by @ghiscoding + +### Bug Fixes + +* allow single row selection via click without checkbox column ([#2311](https://github.com/ghiscoding/slickgrid-universal/issues/2311)) ([b2c6594](https://github.com/ghiscoding/slickgrid-universal/commit/b2c659445c5c39b2b76613c113cef833bdd99f97)) - by @ghiscoding +* only assign CellRangeSelector when Hybrid has `dragToSelect` set ([#2329](https://github.com/ghiscoding/slickgrid-universal/issues/2329)) ([a941819](https://github.com/ghiscoding/slickgrid-universal/commit/a941819649dfa1b54f7887603b930cab27d67c3a)) - by @ghiscoding + ## [9.12.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.11.0...v9.12.0) (2025-12-29) ### Bug Fixes diff --git a/demos/aurelia/package.json b/demos/aurelia/package.json index 84c2c5560d..441828e1cb 100644 --- a/demos/aurelia/package.json +++ b/demos/aurelia/package.json @@ -1,7 +1,7 @@ { "name": "aurelia-slickgrid-demo", "private": true, - "version": "9.12.0", + "version": "10.0.0-beta.0", "description": "Aurelia-Slickgrid demos", "keywords": [ "aurelia", @@ -20,34 +20,37 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@aurelia/fetch-client": "^2.0.0-beta.27", - "@aurelia/i18n": "^2.0.0-beta.27", - "@aurelia/kernel": "^2.0.0-beta.27", - "@aurelia/metadata": "^2.0.0-beta.27", - "@aurelia/router": "^2.0.0-beta.27", - "@aurelia/runtime": "^2.0.0-beta.27", - "@aurelia/runtime-html": "^2.0.0-beta.27", + "@aurelia/fetch-client": "^2.0.0-rc.0", + "@aurelia/i18n": "^2.0.0-rc.0", + "@aurelia/kernel": "^2.0.0-rc.0", + "@aurelia/metadata": "^2.0.0-rc.0", + "@aurelia/router": "^2.0.0-rc.0", + "@aurelia/runtime": "^2.0.0-rc.0", + "@aurelia/runtime-html": "^2.0.0-rc.0", "@faker-js/faker": "catalog:", "@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:*", "@slickgrid-universal/excel-export": "workspace:*", "@slickgrid-universal/graphql": "workspace:*", "@slickgrid-universal/odata": "workspace:*", + "@slickgrid-universal/pdf-export": "workspace:*", "@slickgrid-universal/row-detail-view-plugin": "workspace:*", "@slickgrid-universal/rxjs-observable": "workspace:*", "@slickgrid-universal/text-export": "workspace:*", - "aurelia": "^2.0.0-beta.27", + "aurelia": "^2.0.0-rc.0", "aurelia-slickgrid": "workspace:*", "bootstrap": "catalog:", "i18next": "catalog:", + "jspdf": "catalog:", "rxjs": "catalog:" }, "devDependencies": { - "@aurelia/vite-plugin": "^2.0.0-beta.27", + "@aurelia/vite-plugin": "^2.0.0-rc.0", "@types/fnando__sparkline": "catalog:", "@types/node": "catalog:", "@types/sortablejs": "catalog:", @@ -55,11 +58,10 @@ "cypress": "catalog:", "cypress-real-events": "catalog:", "dompurify": "catalog:", - "npm-run-all2": "catalog:", "sass": "catalog:", "tslib": "catalog:", "typescript": "catalog:", - "vite": "catalog:vite7", + "vite": "catalog:", "vite-plugin-sass-dts": "^1.3.35" } -} \ No newline at end of file +} diff --git a/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json b/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json index 8f1107ea84..b412eea6d6 100644 --- a/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json +++ b/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json @@ -22,6 +22,7 @@ "EXPAND_ALL_GROUPS": "Expand all Groups", "EXPORT_TO_CSV": "Export in CSV format", "EXPORT_TO_EXCEL": "Export to Excel", + "EXPORT_TO_PDF": "Export to PDF", "EXPORT_TO_TAB_DELIMITED": "Export in Text format (Tab delimited)", "EXPORT_TO_TEXT_FORMAT": "Export in Text format", "FILTER_SHORTCUTS": "Filter Shortcuts", diff --git a/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json b/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json index cfe73a2670..c957bb46b8 100644 --- a/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json +++ b/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json @@ -22,6 +22,7 @@ "EXPAND_ALL_GROUPS": "Étendre tous les groupes", "EXPORT_TO_CSV": "Exporter en format CSV", "EXPORT_TO_EXCEL": "Exporter vers Excel", + "EXPORT_TO_PDF": "Exporter vers PDF", "EXPORT_TO_TAB_DELIMITED": "Exporter en format texte (délimité par tabulation)", "EXPORT_TO_TEXT_FORMAT": "Exporter en format texte", "FILTER_SHORTCUTS": "Raccourcis de filtre", 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/example13.html b/demos/aurelia/src/examples/slickgrid/example13.html index b1654224bf..e5a6eaadd6 100644 --- a/demos/aurelia/src/examples/slickgrid/example13.html +++ b/demos/aurelia/src/examples/slickgrid/example13.html @@ -41,6 +41,9 @@

    + @@ -96,6 +99,8 @@

    dataset.bind="dataset" on-before-export-to-excel.trigger="processing = true" on-after-export-to-excel.trigger="processing = false" + on-before-export-to-pdf.trigger="processing = true" + on-after-export-to-pdf.trigger="processing = false" on-aurelia-grid-created.trigger="aureliaGridReady($event.detail)" > diff --git a/demos/aurelia/src/examples/slickgrid/example13.ts b/demos/aurelia/src/examples/slickgrid/example13.ts index a9fbccfab8..20e4ebe4b5 100644 --- a/demos/aurelia/src/examples/slickgrid/example13.ts +++ b/demos/aurelia/src/examples/slickgrid/example13.ts @@ -1,4 +1,5 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { TextExportService } from '@slickgrid-universal/text-export'; import { Aggregators, @@ -27,6 +28,7 @@ export class Example13 { hideSubTitle = false; processing = false; excelExportService = new ExcelExportService(); + pdfExportService = new PdfExportService(); textExportService = new TextExportService(); constructor() { @@ -218,7 +220,7 @@ export class Example13 { enableTextExport: true, excelExportOptions: { sanitizeDataExport: true }, textExportOptions: { sanitizeDataExport: true }, - externalResources: [this.excelExportService, this.textExportService], + externalResources: [this.excelExportService, this.pdfExportService, this.textExportService], showCustomFooter: true, customFooterOptions: { // optionally display some text on the left footer container @@ -226,6 +228,12 @@ export class Example13 { hideTotalItemCount: false, hideLastUpdateTimestamp: false, }, + enablePdfExport: true, + pdfExportOptions: { + repeatHeadersOnEachPage: false, + sanitizeDataExport: true, + documentTitle: 'Grouping Grid', + }, }; } @@ -274,6 +282,10 @@ export class Example13 { }); } + exportToPdf() { + this.pdfExportService.exportToPdf({ filename: 'Export' }); + } + groupByDuration() { // you need to manually add the sort icon(s) in UI this.aureliaGrid.filterService.setSortColumnIcons([{ columnId: 'duration', sortAsc: true }]); 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 9d8edcce5a..f7daa332e4 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.ts +++ b/demos/aurelia/src/examples/slickgrid/example14.ts @@ -1,8 +1,10 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { type AureliaGridInstance, type Column, type GridOption, type ItemMetadata } from 'aurelia-slickgrid'; import './example14.scss'; // provide custom CSS/SASS styling export class Example14 { + aureliaGrid1!: AureliaGridInstance; aureliaGrid2!: AureliaGridInstance; gridObj2: any; columnDefinitions1: Column[] = []; @@ -12,6 +14,7 @@ export class Example14 { dataset1: any[] = []; dataset2: any[] = []; hideSubTitle = false; + isColspanSpreading = false; constructor() { this.definedGrid1(); @@ -24,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; @@ -49,11 +56,12 @@ export class Example14 { preHeaderPanelHeight: 28, gridHeight: 275, gridWidth: 800, + enablePdfExport: true, enableExcelExport: true, excelExportOptions: { exportWithFormatter: false, }, - externalResources: [new ExcelExportService()], + externalResources: [new ExcelExportService(), new PdfExportService()], explicitInitialization: true, dataView: { globalItemMetadataProvider: { @@ -63,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, }; } @@ -96,11 +105,12 @@ export class Example14 { gridHeight: 275, gridWidth: 800, frozenColumn: 2, + enablePdfExport: true, enableExcelExport: true, excelExportOptions: { exportWithFormatter: false, }, - externalResources: [new ExcelExportService()], + externalResources: [new ExcelExportService(), new PdfExportService()], gridMenu: { hideClearFrozenColumnsCommand: false }, headerMenu: { hideFreezeColumnsCommand: false }, }; @@ -153,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.html b/demos/aurelia/src/examples/slickgrid/example18.html index 0e99d1a95f..5cecdb796b 100644 --- a/demos/aurelia/src/examples/slickgrid/example18.html +++ b/demos/aurelia/src/examples/slickgrid/example18.html @@ -64,6 +64,9 @@

    + diff --git a/demos/aurelia/src/examples/slickgrid/example18.ts b/demos/aurelia/src/examples/slickgrid/example18.ts index 5928d02391..24f7665ba3 100644 --- a/demos/aurelia/src/examples/slickgrid/example18.ts +++ b/demos/aurelia/src/examples/slickgrid/example18.ts @@ -1,4 +1,5 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { TextExportService } from '@slickgrid-universal/text-export'; import { Aggregators, @@ -32,6 +33,7 @@ export class Example18 { processing = false; selectedGroupingFields: Array = ['', '', '']; excelExportService = new ExcelExportService(); + pdfExportService = new PdfExportService(); textExportService = new TextExportService(); constructor() { @@ -251,11 +253,17 @@ export class Example18 { initialGroupBy: ['duration'], }, darkMode: this._darkMode, - enableTextExport: true, - enableExcelExport: true, excelExportOptions: { sanitizeDataExport: true }, textExportOptions: { sanitizeDataExport: true }, - externalResources: [this.excelExportService, this.textExportService], + externalResources: [this.excelExportService, this.pdfExportService, this.textExportService], + // -- NOTE: registered resources are auto-enabled + // enableTextExport: true, + // enablePdfExport: true, + // enableExcelExport: true, + pdfExportOptions: { + repeatHeadersOnEachPage: true, // defaults to true + documentTitle: 'Grouping Grid', + }, }; } @@ -317,6 +325,12 @@ export class Example18 { }); } + exportToPdf() { + this.pdfExportService.exportToPdf({ + filename: 'Export', + }); + } + groupByDurationOrderByCount(sortedByCount = false) { this.durationOrderByCount = sortedByCount; this.clearGrouping(false); 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 501a634366..7a4d4beac2 100644 --- a/demos/aurelia/src/examples/slickgrid/example32.ts +++ b/demos/aurelia/src/examples/slickgrid/example32.ts @@ -307,7 +307,6 @@ export class Example32 { options: { minLength: 1, fetch: (searchTerm: string, callback: (items: false | any[]) => void) => { - // const items = require('c://TEMP/items.json'); const products = this.mockProducts(); callback(products.filter((product) => product.itemName.toLowerCase().includes(searchTerm.toLowerCase()))); }, @@ -495,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, }, @@ -586,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 91bf9fc1e6..c16ae4fcc9 100644 --- a/demos/aurelia/src/examples/slickgrid/example50.ts +++ b/demos/aurelia/src/examples/slickgrid/example50.ts @@ -58,10 +58,9 @@ export class Example50 { gridHeight: 225, gridWidth: 800, rowHeight: 33, - enableHybridSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { selectionType: 'row', - selectActiveRow: true, }, }; 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/aurelia/vite.config.mts b/demos/aurelia/vite.config.mts index 2ceb43fce0..f5cba37e54 100644 --- a/demos/aurelia/vite.config.mts +++ b/demos/aurelia/vite.config.mts @@ -1,5 +1,6 @@ -import { defineConfig, type PluginOption } from 'vite'; import aurelia from '@aurelia/vite-plugin'; +import { defineConfig, type PluginOption } from 'vite'; + // import { resolve } from 'path'; export default defineConfig({ @@ -44,4 +45,12 @@ export default defineConfig({ }, }, }, + optimizeDeps: { + include: ['jspdf'], + }, + resolve: { + alias: { + jspdf: 'jspdf/dist/jspdf.es.min.js', + }, + }, }); diff --git a/demos/react/CHANGELOG.md b/demos/react/CHANGELOG.md index 8c35ac4498..e6e4818a5d 100644 --- a/demos/react/CHANGELOG.md +++ b/demos/react/CHANGELOG.md @@ -4,6 +4,53 @@ 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 + +* **export:** add new optional PDF Export package ([#2317](https://github.com/ghiscoding/slickgrid-universal/issues/2317)) ([42347f6](https://github.com/ghiscoding/slickgrid-universal/commit/42347f605388d3c080568d8bbe93dcf7c5ed1ff4)) - by @ghiscoding + +### Bug Fixes + +* allow single row selection via click without checkbox column ([#2311](https://github.com/ghiscoding/slickgrid-universal/issues/2311)) ([b2c6594](https://github.com/ghiscoding/slickgrid-universal/commit/b2c659445c5c39b2b76613c113cef833bdd99f97)) - by @ghiscoding +* **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 +* **deps:** update all non-major dependencies ([#2320](https://github.com/ghiscoding/slickgrid-universal/issues/2320)) ([9d9f7ad](https://github.com/ghiscoding/slickgrid-universal/commit/9d9f7ad9c0d2b7b3ee768b8061dfd646dc75bb81)) - by @renovate-bot +* **deps:** update all non-major dependencies ([#2342](https://github.com/ghiscoding/slickgrid-universal/issues/2342)) ([3b2a921](https://github.com/ghiscoding/slickgrid-universal/commit/3b2a921ff5cae8de5da78f36d7a625fa79eeb557)) - by @renovate-bot +* only assign CellRangeSelector when Hybrid has `dragToSelect` set ([#2329](https://github.com/ghiscoding/slickgrid-universal/issues/2329)) ([a941819](https://github.com/ghiscoding/slickgrid-universal/commit/a941819649dfa1b54f7887603b930cab27d67c3a)) - by @ghiscoding + ## [9.12.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.11.0...v9.12.0) (2025-12-29) ### Bug Fixes diff --git a/demos/react/package.json b/demos/react/package.json index fd23fb5c1c..606d096eb3 100644 --- a/demos/react/package.json +++ b/demos/react/package.json @@ -1,7 +1,7 @@ { "name": "slickgrid-react-demo", "private": true, - "version": "9.12.0", + "version": "10.0.0-beta.0", "description": "Slickgrid-React demo", "keywords": [ "react", @@ -30,16 +30,18 @@ "@slickgrid-universal/graphql": "workspace:*", "@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.3", - "react-dom": "^19.2.3", - "react-i18next": "^16.5.1", - "react-router-dom": "^7.11.0", + "react": "catalog:", + "react-dom": "catalog:", + "react-i18next": "^16.5.4", + "react-router": "^7.13.0", "slickgrid-react": "workspace:*" }, "peerDependencies": { @@ -53,8 +55,8 @@ "@popperjs/core": "catalog:", "@types/fnando__sparkline": "catalog:", "@types/node": "catalog:", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", "@types/sortablejs": "catalog:", "@vitejs/plugin-react": "catalog:", "cross-env": "catalog:", @@ -64,7 +66,7 @@ "rxjs": "catalog:", "sass": "catalog:", "typescript": "catalog:", - "vite": "catalog:vite7", + "vite": "catalog:", "vite-tsconfig-paths": "catalog:" } -} \ No newline at end of file +} diff --git a/demos/react/src/assets/locales/en/translation.json b/demos/react/src/assets/locales/en/translation.json index 8f1107ea84..b412eea6d6 100644 --- a/demos/react/src/assets/locales/en/translation.json +++ b/demos/react/src/assets/locales/en/translation.json @@ -22,6 +22,7 @@ "EXPAND_ALL_GROUPS": "Expand all Groups", "EXPORT_TO_CSV": "Export in CSV format", "EXPORT_TO_EXCEL": "Export to Excel", + "EXPORT_TO_PDF": "Export to PDF", "EXPORT_TO_TAB_DELIMITED": "Export in Text format (Tab delimited)", "EXPORT_TO_TEXT_FORMAT": "Export in Text format", "FILTER_SHORTCUTS": "Filter Shortcuts", diff --git a/demos/react/src/assets/locales/fr/translation.json b/demos/react/src/assets/locales/fr/translation.json index cfe73a2670..c957bb46b8 100644 --- a/demos/react/src/assets/locales/fr/translation.json +++ b/demos/react/src/assets/locales/fr/translation.json @@ -22,6 +22,7 @@ "EXPAND_ALL_GROUPS": "Étendre tous les groupes", "EXPORT_TO_CSV": "Exporter en format CSV", "EXPORT_TO_EXCEL": "Exporter vers Excel", + "EXPORT_TO_PDF": "Exporter vers PDF", "EXPORT_TO_TAB_DELIMITED": "Exporter en format texte (délimité par tabulation)", "EXPORT_TO_TEXT_FORMAT": "Exporter en format texte", "FILTER_SHORTCUTS": "Raccourcis de filtre", diff --git a/demos/react/src/examples/slickgrid/App.tsx b/demos/react/src/examples/slickgrid/App.tsx index f37ba66fd3..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-dom'; +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 03bdbb5ae4..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, @@ -29,10 +28,10 @@ const Example12: React.FC = () => { const [gridOptions, setGridOptions] = useState(undefined); const [selectedLanguage, setSelectedLanguage] = useState(defaultLang); const [hideSubTitle, setHideSubTitle] = useState(false); + const [excelExportService] = useState(new ExcelExportService()); + const [textExportService] = useState(new TextExportService()); const reactGridRef = useRef(null); - const excelExportService = new ExcelExportService(); - const textExportService = new TextExportService(); let duplicateTitleHeaderCount = 1; useEffect(() => { @@ -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/Example13.tsx b/demos/react/src/examples/slickgrid/Example13.tsx index 8d7a5c6b32..8910f3c73b 100644 --- a/demos/react/src/examples/slickgrid/Example13.tsx +++ b/demos/react/src/examples/slickgrid/Example13.tsx @@ -1,4 +1,5 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { TextExportService } from '@slickgrid-universal/text-export'; import React, { useEffect, useRef, useState } from 'react'; import { @@ -24,9 +25,9 @@ const Example13: React.FC = () => { const reactGridRef = useRef(null); const [processing, setProcessing] = useState(false); const [hideSubTitle, setHideSubTitle] = useState(false); - - const excelExportService = new ExcelExportService(); - const textExportService = new TextExportService(); + const [excelExportService] = useState(new ExcelExportService()); + const [pdfExportService] = useState(new PdfExportService()); + const [textExportService] = useState(new TextExportService()); useEffect(() => { defineGrid(); @@ -209,7 +210,7 @@ const Example13: React.FC = () => { enableTextExport: true, excelExportOptions: { sanitizeDataExport: true }, textExportOptions: { sanitizeDataExport: true }, - externalResources: [excelExportService, textExportService], + externalResources: [excelExportService, pdfExportService, textExportService], showCustomFooter: true, customFooterOptions: { // optionally display some text on the left footer container @@ -217,6 +218,12 @@ const Example13: React.FC = () => { hideTotalItemCount: false, hideLastUpdateTimestamp: false, }, + enablePdfExport: true, + pdfExportOptions: { + repeatHeadersOnEachPage: false, + sanitizeDataExport: true, + documentTitle: 'Grouping Grid', + }, }; setColumnDefinitions(columnDefinitions); @@ -273,6 +280,10 @@ const Example13: React.FC = () => { }); } + function exportToPdf() { + pdfExportService.exportToPdf({ filename: 'Export' }); + } + function groupByDuration() { // you need to manually add the sort icon(s) in UI reactGridRef.current?.filterService.setSortColumnIcons([{ columnId: 'duration', sortAsc: true }]); @@ -435,9 +446,12 @@ const Example13: React.FC = () => { - +

    @@ -499,6 +513,8 @@ const Example13: React.FC = () => { dataset={dataset} onBeforeExportToExcel={() => changeProcessing(true)} onAfterExportToExcel={() => changeProcessing(false)} + onBeforeExportToPdf={() => changeProcessing(true)} + onAfterExportToPdf={() => changeProcessing(false)} onReactGridCreated={($event) => reactGridReady($event.detail)} /> diff --git a/demos/react/src/examples/slickgrid/Example14.tsx b/demos/react/src/examples/slickgrid/Example14.tsx index 47f0542510..de8d8162e1 100644 --- a/demos/react/src/examples/slickgrid/Example14.tsx +++ b/demos/react/src/examples/slickgrid/Example14.tsx @@ -1,4 +1,5 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import React, { useEffect, useRef, useState } from 'react'; import { SlickgridReact, type Column, type GridOption, type ItemMetadata, type SlickgridReactInstance } from 'slickgrid-react'; import './example14.scss'; // provide custom CSS/SASS styling @@ -6,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); @@ -21,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; @@ -53,7 +55,8 @@ const Example14: React.FC = () => { excelExportOptions: { exportWithFormatter: false, }, - externalResources: [new ExcelExportService()], + enablePdfExport: true, + externalResources: [new ExcelExportService(), new PdfExportService()], explicitInitialization: true, dataView: { globalItemMetadataProvider: { @@ -103,7 +106,7 @@ const Example14: React.FC = () => { excelExportOptions: { exportWithFormatter: false, }, - externalResources: [new ExcelExportService()], + externalResources: [new ExcelExportService(), new PdfExportService()], gridMenu: { hideClearFrozenColumnsCommand: false }, headerMenu: { hideFreezeColumnsCommand: false }, }; @@ -160,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 ? ( '' ) : ( @@ -198,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 ea56182327..2deea88e13 100644 --- a/demos/react/src/examples/slickgrid/Example18.tsx +++ b/demos/react/src/examples/slickgrid/Example18.tsx @@ -1,4 +1,5 @@ import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { TextExportService } from '@slickgrid-universal/text-export'; import React, { useEffect, useRef, useState } from 'react'; import { @@ -25,9 +26,9 @@ const Example18: React.FC = () => { const [draggableGroupingPlugin, setDraggableGroupingPlugin] = useState(null); const [selectedGroupingFields, setSelectedGroupingFields] = useState(['', '', '']); const [hideSubTitle, setHideSubTitle] = useState(false); - - const excelExportService = new ExcelExportService(); - const textExportService = new TextExportService(); + const [excelExportService] = useState(new ExcelExportService()); + const [pdfExportService] = useState(new PdfExportService()); + const [textExportService] = useState(new TextExportService()); useEffect(() => { defineGrid(); @@ -244,11 +245,17 @@ const Example18: React.FC = () => { initialGroupBy: ['duration'], }, darkMode, - enableTextExport: true, - enableExcelExport: true, excelExportOptions: { sanitizeDataExport: true }, textExportOptions: { sanitizeDataExport: true }, - externalResources: [excelExportService, textExportService], + 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); @@ -341,6 +348,12 @@ const Example18: React.FC = () => { }); } + function exportToPdf() { + pdfExportService.exportToPdf({ + filename: 'Export', + }); + } + function groupByDurationOrderByCount(sortedByCount = false) { setDurationOrderByCount(sortedByCount); clearGrouping(false); @@ -535,6 +548,9 @@ const Example18: React.FC = () => { + 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 4161f97c4b..220bfb1eba 100644 --- a/demos/react/src/examples/slickgrid/Example32.tsx +++ b/demos/react/src/examples/slickgrid/Example32.tsx @@ -303,7 +303,6 @@ const Example32: React.FC = () => { options: { minLength: 1, fetch: (searchTerm: string, callback: (items: false | any[]) => void) => { - // const items = require('c://TEMP/items.json'); const products = mockProducts(); callback(products.filter((product) => product.itemName.toLowerCase().includes(searchTerm.toLowerCase()))); }, @@ -492,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, }, @@ -586,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 d0d4ef9194..f1b82d6265 100644 --- a/demos/react/src/examples/slickgrid/Example48.tsx +++ b/demos/react/src/examples/slickgrid/Example48.tsx @@ -91,11 +91,10 @@ const Example48: React.FC = () => { externalResources: [new ExcelExportService()], // enable new hybrid selection model (rows & cells) - enableHybridSelection: true, - rowSelectionOptions: { - // True (Single Selection), False (Multiple Selections) - selectActiveRow: true, + enableSelection: true, + selectionOptions: { rowSelectColumnIds: ['id'], + selectionType: 'mixed', }, // when using the ExcelCopyBuffer, you can see what the selection range is @@ -113,13 +112,16 @@ 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, // you could use "row" selection to override the hybrid mode selectionType: 'row', + + // allow using the mouse drag selection to select multiple rows + dragToSelect: true, }, }; @@ -210,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 c91d8cb0de..d316e3b060 100644 --- a/demos/react/src/examples/slickgrid/Example50.tsx +++ b/demos/react/src/examples/slickgrid/Example50.tsx @@ -54,10 +54,9 @@ const Example50: React.FC = () => { gridHeight: 225, gridWidth: 800, rowHeight: 33, - enableHybridSelection: true, - rowSelectionOptions: { + enableSelection: true, + selectionOptions: { selectionType: 'row', - selectActiveRow: true, }, }; 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/src/index.tsx b/demos/react/src/index.tsx index 628dac5dc4..c78c7b3cfa 100644 --- a/demos/react/src/index.tsx +++ b/demos/react/src/index.tsx @@ -2,7 +2,7 @@ import 'bootstrap'; import i18n from 'i18next'; import { createRoot } from 'react-dom/client'; import { initReactI18next } from 'react-i18next'; -import { HashRouter } from 'react-router-dom'; +import { HashRouter } from 'react-router'; import { I18nextProvider } from 'slickgrid-react'; import localeEn from './assets/locales/en/translation.json'; import localeFr from './assets/locales/fr/translation.json'; 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 20855ac1ae..3c3d9f0ab0 100644 --- a/demos/vanilla/CHANGELOG.md +++ b/demos/vanilla/CHANGELOG.md @@ -4,6 +4,48 @@ 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 + +* add minus icon for partial row selection ([#2322](https://github.com/ghiscoding/slickgrid-universal/issues/2322)) ([d3dc6e7](https://github.com/ghiscoding/slickgrid-universal/commit/d3dc6e7772243a3d0dd6961680698d05a834ae58)) - by @ghiscoding +* **export:** add new optional PDF Export package ([#2317](https://github.com/ghiscoding/slickgrid-universal/issues/2317)) ([42347f6](https://github.com/ghiscoding/slickgrid-universal/commit/42347f605388d3c080568d8bbe93dcf7c5ed1ff4)) - by @ghiscoding + +### Bug Fixes + +* allow single row selection via click without checkbox column ([#2311](https://github.com/ghiscoding/slickgrid-universal/issues/2311)) ([b2c6594](https://github.com/ghiscoding/slickgrid-universal/commit/b2c659445c5c39b2b76613c113cef833bdd99f97)) - by @ghiscoding +* only assign CellRangeSelector when Hybrid has `dragToSelect` set ([#2329](https://github.com/ghiscoding/slickgrid-universal/issues/2329)) ([a941819](https://github.com/ghiscoding/slickgrid-universal/commit/a941819649dfa1b54f7887603b930cab27d67c3a)) - by @ghiscoding + ## [9.12.0](https://github.com/ghiscoding/slickgrid-universal/compare/v9.11.0...v9.12.0) (2025-12-29) ### Features diff --git a/demos/vanilla/package.json b/demos/vanilla/package.json index d428ba9882..606bae1b01 100644 --- a/demos/vanilla/package.json +++ b/demos/vanilla/package.json @@ -1,7 +1,7 @@ { "name": "vanilla-demo", "private": true, - "version": "9.12.0", + "version": "10.0.0-beta.0", "scripts": { "build": "pnpm type-check && vite build", "type-check": "tsc --noEmit", @@ -21,6 +21,7 @@ "@slickgrid-universal/excel-export": "workspace:*", "@slickgrid-universal/graphql": "workspace:*", "@slickgrid-universal/odata": "workspace:*", + "@slickgrid-universal/pdf-export": "workspace:*", "@slickgrid-universal/row-detail-view-plugin": "workspace:*", "@slickgrid-universal/rxjs-observable": "workspace:*", "@slickgrid-universal/text-export": "workspace:*", @@ -37,6 +38,6 @@ "@types/node": "catalog:", "sass": "catalog:", "typescript": "catalog:", - "vite": "catalog:vite7" + "vite": "catalog:" } } diff --git a/demos/vanilla/public/i18n/en.json b/demos/vanilla/public/i18n/en.json index ce97243eb7..600083fa11 100644 --- a/demos/vanilla/public/i18n/en.json +++ b/demos/vanilla/public/i18n/en.json @@ -23,6 +23,7 @@ "EXPAND_ALL_GROUPS": "Expand all Groups", "EXPORT_TO_CSV": "Export in CSV format", "EXPORT_TO_EXCEL": "Export to Excel", + "EXPORT_TO_PDF": "Export to PDF", "EXPORT_TO_TAB_DELIMITED": "Export in Text format (Tab delimited)", "EXPORT_TO_TEXT_FORMAT": "Export in Text format", "FILTER_SHORTCUTS": "Filter Shortcuts", diff --git a/demos/vanilla/public/i18n/fr.json b/demos/vanilla/public/i18n/fr.json index f5068d3a91..084d95bbb8 100644 --- a/demos/vanilla/public/i18n/fr.json +++ b/demos/vanilla/public/i18n/fr.json @@ -23,6 +23,7 @@ "EXPAND_ALL_GROUPS": "Étendre tous les groupes", "EXPORT_TO_CSV": "Exporter en format CSV", "EXPORT_TO_EXCEL": "Exporter vers Excel", + "EXPORT_TO_PDF": "Exporter vers PDF", "EXPORT_TO_TAB_DELIMITED": "Exporter en format texte (délimité par tabulation)", "EXPORT_TO_TEXT_FORMAT": "Exporter en format texte", "FILTER_SHORTCUTS": "Raccourcis de filtre", 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/example02.html b/demos/vanilla/src/examples/example02.html index ea7ed22d6f..bc29fe6e78 100644 --- a/demos/vanilla/src/examples/example02.html +++ b/demos/vanilla/src/examples/example02.html @@ -33,6 +33,10 @@

    Export to Excel + diff --git a/demos/vanilla/src/examples/example02.ts b/demos/vanilla/src/examples/example02.ts index 6adc9bf0ce..452b7d26f5 100644 --- a/demos/vanilla/src/examples/example02.ts +++ b/demos/vanilla/src/examples/example02.ts @@ -13,6 +13,7 @@ import { type SliderOption, } 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'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { ExampleGridOptions } from './example-grid-options.js'; @@ -32,12 +33,14 @@ export default class Example02 { commandQueue = []; sgb: SlickVanillaGridBundle; excelExportService: ExcelExportService; + pdfExportService: PdfExportService; loadingClass = ''; sortStart = 0; constructor() { this.excelExportService = new ExcelExportService(); this._bindingEventService = new BindingEventService(); + this.pdfExportService = new PdfExportService(); } attached() { @@ -47,10 +50,10 @@ export default class Example02 { this._bindingEventService.bind( gridContainerElm, - 'onbeforeexporttoexcel', + ['onbeforeexporttoexcel', 'onbeforeexporttopdf'], () => (this.loadingClass = 'mdi mdi-load mdi-spin-1s font-22px') ); - this._bindingEventService.bind(gridContainerElm, 'onafterexporttoexcel', () => (this.loadingClass = '')); + this._bindingEventService.bind(gridContainerElm, ['onafterexporttoexcel', 'onafterexporttopdf'], () => (this.loadingClass = '')); this._bindingEventService.bind(gridContainerElm, 'onbeforesort', () => { // console.time('sort'); this.sortStart = window.performance.now(); @@ -152,6 +155,7 @@ export default class Example02 { minWidth: 70, width: 90, formatter: Formatters.percentCompleteBar, + exportWithFormatter: false, filterable: true, filter: { model: Filters.compoundSlider }, sortable: true, @@ -267,6 +271,7 @@ export default class Example02 { editable: true, enableCellNavigation: true, enableTextExport: true, + enablePdfExport: true, enableFiltering: true, enableGrouping: true, columnPicker: { @@ -299,8 +304,12 @@ export default class Example02 { sheet.data.push([{ value: customTitle, metadata: { style: excelFormat.id } }]); }, }, + pdfExportOptions: { + repeatHeadersOnEachPage: false, + documentTitle: 'Grouping Grid', + }, textExportOptions: { filename: 'my-export', sanitizeDataExport: true }, - externalResources: [this.excelExportService, new TextExportService()], + externalResources: [this.excelExportService, this.pdfExportService, new TextExportService()], showCustomFooter: true, // display some metrics in the bottom custom footer customFooterOptions: { // optionally display some text on the left footer container @@ -367,6 +376,10 @@ export default class Example02 { this.excelExportService.exportToExcel({ filename: 'export', format: 'xlsx' }); } + exportToPdf() { + this.pdfExportService.exportToPdf({ filename: 'Export' }); + } + groupByDuration() { // you need to manually add the sort icon(s) in UI this.sgb?.slickGrid?.setSortColumns([{ columnId: 'duration', sortAsc: true }]); diff --git a/demos/vanilla/src/examples/example03.html b/demos/vanilla/src/examples/example03.html index 27e49f0e20..f271da75db 100644 --- a/demos/vanilla/src/examples/example03.html +++ b/demos/vanilla/src/examples/example03.html @@ -42,6 +42,10 @@

    Export to Excel + diff --git a/demos/vanilla/src/examples/example03.ts b/demos/vanilla/src/examples/example03.ts index 5e26b0a583..c2a023b836 100644 --- a/demos/vanilla/src/examples/example03.ts +++ b/demos/vanilla/src/examples/example03.ts @@ -17,6 +17,7 @@ import { type VanillaCalendarOption, } 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'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { ExampleGridOptions } from './example-grid-options.js'; @@ -41,6 +42,7 @@ export default class Example03 { dataset: any[]; editCommandQueue: EditCommand[] = []; excelExportService: ExcelExportService; + pdfExportService: PdfExportService; sgb: SlickVanillaGridBundle; durationOrderByCount = false; draggableGroupingPlugin: SlickDraggableGrouping; @@ -50,6 +52,7 @@ export default class Example03 { constructor() { this._bindingEventService = new BindingEventService(); this.excelExportService = new ExcelExportService(); + this.pdfExportService = new PdfExportService(); } attached() { @@ -63,10 +66,10 @@ export default class Example03 { this._bindingEventService.bind(gridContainerElm, 'onitemsdeleted', this.handleItemsDeleted.bind(this)); this._bindingEventService.bind( gridContainerElm, - 'onbeforeexporttoexcel', + ['onbeforeexporttoexcel', 'onbeforeexporttopdf'], () => (this.loadingClass = 'mdi mdi-load mdi-spin-1s font-22px') ); - this._bindingEventService.bind(gridContainerElm, 'onafterexporttoexcel', () => (this.loadingClass = '')); + this._bindingEventService.bind(gridContainerElm, ['onafterexporttoexcel', 'onafterexporttopdf'], () => (this.loadingClass = '')); this.sgb = new Slicker.GridBundle( gridContainerElm, this.columnDefinitions, @@ -97,6 +100,9 @@ export default class Example03 { minLength: 5, maxLength: 255, }, + pdfExportOptions: { + textAlign: 'center', + }, filterable: true, grouping: { getter: 'title', @@ -133,6 +139,10 @@ export default class Example03 { aggregateCollapsed: false, collapsed: false, }, + pdfExportOptions: { + width: 70, + textAlign: 'center', + }, }, { id: 'start', @@ -345,14 +355,21 @@ export default class Example03 { enableAutoSizeColumns: true, enableAutoResize: true, enableCellNavigation: true, - enableTextExport: true, - enableExcelExport: true, excelExportOptions: { exportWithFormatter: true, }, - externalResources: [new TextExportService(), this.excelExportService], + pdfExportOptions: { + pageOrientation: 'landscape', + exportWithFormatter: true, + 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, }, @@ -390,7 +407,7 @@ export default class Example03 { grouping: ['duration'], }, enableCheckboxSelector: true, - enableRowSelection: true, + enableSelection: true, checkboxSelector: { hideInFilterHeaderRow: false, hideInColumnTitleRow: true, @@ -407,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); @@ -485,6 +502,10 @@ export default class Example03 { }); } + exportToPdf() { + this.pdfExportService.exportToPdf({ filename: 'Export' }); + } + groupByDurationOrderByCount(sortedByCount = false) { this.durationOrderByCount = sortedByCount; this.clearGrouping(false); 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 0aac4876d3..4098b5200b 100644 --- a/demos/vanilla/src/examples/example08.ts +++ b/demos/vanilla/src/examples/example08.ts @@ -1,5 +1,6 @@ -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'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { ExampleGridOptions } from './example-grid-options.js'; @@ -15,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(); @@ -73,6 +75,7 @@ export default class Example08 { gridHeight: 275, gridWidth: 800, enableTextExport: true, + enablePdfExport: true, enableExcelExport: true, excelExportOptions: { exportWithFormatter: true, @@ -81,7 +84,7 @@ export default class Example08 { gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, - externalResources: [new TextExportService(), new ExcelExportService()], + externalResources: [new TextExportService(), new ExcelExportService(), new PdfExportService()], enableCellNavigation: true, enableColumnReorder: false, enableSorting: true, @@ -95,6 +98,7 @@ export default class Example08 { getRowMetadata: (item: any, row: number) => this.renderDifferentColspan(item, row), }, }, + spreadHiddenColspan: this.isColspanSpreading, }; } @@ -249,7 +253,7 @@ export default class Example08 { } selectedOperatorChanged(newOperator: string) { - this.grid2SelectedOperator = newOperator as OperatorString; + this.grid2SelectedOperator = newOperator as OperatorType; this.updateFilter(); } @@ -263,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.html b/demos/vanilla/src/examples/example32.html index 1e6511b1b9..78bf8e90bf 100644 --- a/demos/vanilla/src/examples/example32.html +++ b/demos/vanilla/src/examples/example32.html @@ -72,6 +72,10 @@

    Export to Excel +

    diff --git a/demos/vanilla/src/examples/example32.ts b/demos/vanilla/src/examples/example32.ts index aad62c4c16..8b35a7affd 100644 --- a/demos/vanilla/src/examples/example32.ts +++ b/demos/vanilla/src/examples/example32.ts @@ -1,6 +1,7 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { Editors, type Column, type GridOption, type ItemMetadata } from '@slickgrid-universal/common'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; +import { PdfExportService } from '@slickgrid-universal/pdf-export'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { ExampleGridOptions } from './example-grid-options.js'; import './example32.scss'; @@ -12,6 +13,7 @@ export default class Example32 { gridOptions: GridOption; dataset: any[] = []; excelExportService: ExcelExportService; + pdfExportService: PdfExportService; sgb: SlickVanillaGridBundle; gridContainerElm: HTMLDivElement; showEmployeeId = true; @@ -112,6 +114,7 @@ export default class Example32 { constructor() { this._bindingEventService = new BindingEventService(); this.excelExportService = new ExcelExportService(); + this.pdfExportService = new PdfExportService(); } attached() { @@ -168,7 +171,12 @@ export default class Example32 { enableCellRowSpan: true, enableHeaderMenu: false, enableExcelExport: true, - externalResources: [this.excelExportService], + enablePdfExport: true, + pdfExportOptions: { + fontSize: 7, + headerFontSize: 8, + }, + externalResources: [this.excelExportService, this.pdfExportService], enableExcelCopyBuffer: true, autoEdit: true, editable: false, @@ -191,6 +199,10 @@ export default class Example32 { this.excelExportService.exportToExcel({ filename: 'export', format: 'xlsx' }); } + exportToPdf() { + this.pdfExportService.exportToPdf({ filename: 'export', pageOrientation: 'landscape' }); + } + navigateDown() { this.sgb.slickGrid?.navigateDown(); } @@ -446,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 { @@ -454,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 @@