ngx-datatable icon indicating copy to clipboard operation
ngx-datatable copied to clipboard

feat: add touch support for reorderable and resizable features

Open ColinFrick opened this issue 8 months ago • 4 comments

  • Enabled touch event handling in key directives such as draggable, resizeable, and long-press, ensuring compatibility with touch devices.
  • Introduced utility functions for identifying touch events and extracting event coordinates.
  • Updated styles to enhance usability on touch devices.

What kind of change does this PR introduce? (check one with "x")

  • [ ] Bugfix
  • [x] Feature
  • [ ] Code style update (formatting, local variables)
  • [ ] Refactoring (no functional changes, no api changes)
  • [ ] Build related changes
  • [ ] CI related changes
  • [ ] Other... Please describe:

What is the current behavior? (You can also link to an open issue here) Columns can't be resized or reordered.

What is the new behavior? Columns can be resized and reordered.

Does this PR introduce a breaking change? (check one with "x")

  • [ ] Yes
  • [x] No

If this PR contains a breaking change, please describe the impact and migration path for existing applications: ...

Other information:

ColinFrick avatar Mar 20 '25 12:03 ColinFrick

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Mar 20 '25 12:03 CLAassistant

Sure I'll look into Playwright some time this week.

I did find an open issue talking about support for Touch Events: https://github.com/microsoft/playwright/issues/2903 But I'll investigate further if there is a workaround.

ColinFrick avatar Mar 24 '25 15:03 ColinFrick

I think in that case you can also skip adding playwright tests. Since we anyway have to construct the event manually unit tests will do. But there is now one existing test failing (e2e/sorting.spec.ts:85 ). This is the relevant error message:

Error: Timed out 5000ms waiting for expect(locator).toHaveClass(expected)

Locator: locator('datatable-header-cell[title="Company"]')
Expected pattern: /sort-desc/
Received string:  "datatable-header-cell resizeable sortable sort-active sort-asc longpress"
Call log:
  - expect.toHaveClass with timeout 5000ms
  - waiting for locator('datatable-header-cell[title="Company"]')
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc longpress"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc longpress"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc longpress"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc longpress"
  -   locator resolved to <datatable-header-cell draggable="" tabindex="0" resizeable="" long-press="" title="Company" role="columnheader" ng-reflect-drag-x="true" ng-reflect-drag-y="false" ng-reflect-sort-type="single" ng-reflect-press-enabled="true" ng-reflect-header-height="50px" ng-reflect-resize-enabled="true" ng-reflect-sorts="[object Object]" ng-reflect-column="[object Object]" ng-reflect-all-rows-selected="false" ng-reflect-drag-model="[object Object]" ng-reflect-press-model="[object Object]" ng-reflect-enable-clearing-sort-state="fa…>…</datatable-header-cell>
  -   unexpected value "datatable-header-cell resizeable sortable sort-active sort-asc longpress"


  106 |
  107 |       await expect(companyHeader).toHaveClass(/sort-active/);
> 108 |       await expect(companyHeader).toHaveClass(/sort-desc/);
      |                                   ^
  109 |
  110 |       await expect(loadingIndicator).toBeVisible();
  111 |

    at /e2e/playwright/e2e/sorting.spec.ts:108:35

spike-rabbit avatar Mar 25 '25 07:03 spike-rabbit

Just one more thing, my previous comment broke the code on Firefox. Seems like the desktop version does not support ToucheEvent, so please write the check like this: const isMouse = event instanceof MouseEvent;

spike-rabbit avatar Mar 25 '25 07:03 spike-rabbit

I have updated the PR with the MouseEvent change, but sadly the latest changes completely broke the PR.

I'll have to look over, and re-implement some stuff.

And I still haven't found any option for playwright touch events.

ColinFrick avatar Apr 03 '25 14:04 ColinFrick

I rebased the pull request.

I skipped Playwright and Unit test for now, if that's ok for you.

ColinFrick avatar Apr 23 '25 13:04 ColinFrick

thx a lot. Your changes are looking good.

We are currently facing CI issues, most likely due to github changing terms of use without informing us and we are now running int git lfs quota issues. Once those are resolved I will merge your PR.

spike-rabbit avatar Apr 24 '25 15:04 spike-rabbit

FYI, we are still having the LFS issues. Github support should be on it, but so far they were not able to fix anything.

spike-rabbit avatar May 08 '25 07:05 spike-rabbit

@ColinFrick again, we're extremely sorry that this PR takes so long, we're still waiting for GitHub's support to resolve the Git LFS isssue for good. However, we were able to come up with a workaround so that the builds should work again as expected.

I sadly don't have the rights to rebase your PR or trigger another action run. Would you mind to quickly give it a rebase, so the pipeline passes? I'll then accept the PR ASAP. Many thanks in advance 🙇

fh1ch avatar May 27 '25 14:05 fh1ch

@fh1ch Don't sweat it. I have merged the upstream into the PR, and fixed any merge conflicts.

ColinFrick avatar May 27 '25 14:05 ColinFrick

Seems like there are still conflicts and also a format issue.

Ideally you do not merge master-origin into your branch but rebase onto our master. That should solve the issue.

spike-rabbit avatar May 27 '25 15:05 spike-rabbit

@spike-rabbit Sorry about that. I have rebased the PR and run prettier.

ColinFrick avatar May 28 '25 06:05 ColinFrick

@fh1ch Thanks for the merge!

OT Are you planning to migrate to signal inputs? And are you accepting PRs, respectively? The package would benefit from a centralized signal state management instead of prop drilling.

ColinFrick avatar May 28 '25 14:05 ColinFrick

@ColinFrick yes, we are also planning to do this later this year. Right now the focus is on improving types and solving the most broken parts.. Once this is done, the next step would be indeed migrating to signals and clean-up the whole property passing mess. After that we will finally fix all the real issues, like responsive behavior and a11y.

I will message you directly so we can talk.

spike-rabbit avatar Jun 02 '25 11:06 spike-rabbit