Skip to content

Commit

Permalink
Dev (#69)
Browse files Browse the repository at this point in the history
* more tests

* enhanceSortableAccessibility can hopefully be imported better

* main and module fields in package.json + version bump
  • Loading branch information
tofsjonas authored Feb 14, 2024
1 parent c2a6fbf commit c6826cf
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 21 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.2.0] - 2024-02-14

### Added

- Introduced `main` and `module` fields in `package.json` to enhance compatibility with modern JavaScript tooling and environments.

## [3.1.0] - 2023-11-16

### Changed
Expand Down Expand Up @@ -118,6 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

This CHANGELOG.md was generated with the assistance of [ChatGPT by OpenAI](https://www.openai.com/research/chatgpt).

[3.2.0]: https://github.com/tofsjonas/sortable/releases/tag/3.2.0
[3.1.0]: https://github.com/tofsjonas/sortable/releases/tag/3.1.0
[3.0.0]: https://github.com/tofsjonas/sortable/releases/tag/3.0.0
[2.4.0]: https://github.com/tofsjonas/sortable/releases/tag/2.4.0
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,12 +406,19 @@ Sortable is not very accessible in its raw form. It does not support screen read

By including the file the global function `enhanceSortableAccessibility` will automatically run through all existing `.sortable` tables, but you can also run it manually, like so:

```javascript
```js
enhanceSortableAccessibility([table1, table2,...etc.])
```

The function adds an `aria-label` to each th, as well as `tabindex="0"` to each th in the thead of each table, making it possible to tab through the headers. It updates the `aria-label` depending on the direction.

if you want to import it instead this _should_ work: (I haven't tested it)

```ts
import { enhanceSortableAccessibility } from 'sortable-tablesort/enhanceSortableAccessibility'
enhanceSortableAccessibility([table1, table2,...etc.])
```

## Sort on load

If you wish to sort a table on load, I would recommend doing something like this:
Expand Down
9 changes: 9 additions & 0 deletions enhanceSortableAccessibility.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* This is a "plugin" for the sortable package:
* https://www.npmjs.com/package/sortable-tablesort
* https://github.com/tofsjonas/sortable
*
* Enhances the accessibility of class="sortable" tables by adding ARIA attributes and keyboard event listeners.
* @param tables - A list of HTML table elements to enhance.
*/
export declare const enhanceSortableAccessibility: (tables: NodeListOf<HTMLTableElement>) => void;
68 changes: 68 additions & 0 deletions enhanceSortableAccessibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* This is a "plugin" for the sortable package:
* https://www.npmjs.com/package/sortable-tablesort
* https://github.com/tofsjonas/sortable
*
* Enhances the accessibility of class="sortable" tables by adding ARIA attributes and keyboard event listeners.
* @param tables - A list of HTML table elements to enhance.
*/
var enhanceSortableAccessibility = function (tables) {
/**
* Generates an aria-label attribute for a table header cell based on its content and current sort direction.
* @param element - The table header cell to update.
* @param default_direction - The default sort direction for the table.
*/
function updateAriaLabel(element, default_direction) {
var _a;
if (default_direction === void 0) { default_direction = ''; }
// Generate aria-label based on header content
var header_text = element.textContent || 'element';
var current_direction = (_a = element.getAttribute('aria-sort')) !== null && _a !== void 0 ? _a : '';
var new_direction = 'descending';
if (current_direction === 'descending' || (default_direction && current_direction !== 'ascending')) {
new_direction = 'ascending';
}
var aria_label = "Click to sort table by ".concat(header_text, " in ").concat(new_direction, " order");
element.setAttribute('aria-label', aria_label);
// element.setAttribute('title', aria_label) REMEMBER TO COMMENT OUT WHEN NOT TESTING!!
}
/**
* Handles keyboard events on table header cells and triggers a click event when the Enter key is pressed.
* @param event - The keyboard event to handle.
*/
function handleKeyDown(event) {
if (event.key === 'Enter') {
var element = event.target;
element.click();
}
}
// Iterate over each table in the input list
tables.forEach(function (table) {
var default_direction = table.classList.contains('asc') ? 'ascending' : '';
var headers = table.querySelectorAll('th');
// Iterate over each header cell in the table
headers.forEach(function (header) {
var element = header;
// Skip if the header cell already has a tabindex attribute
if (element.hasAttribute('tabindex'))
return;
var update = function () {
updateAriaLabel(element, default_direction);
};
// Add tabindex attribute and generate initial aria-label attribute
element.setAttribute('tabindex', '0');
update();
// Attach click event listener to update aria-label attribute
element.addEventListener('click', function () {
// Add a delay to allow the new sort order to be applied
setTimeout(update, 50);
});
// Attach focus event listener to update aria-label attribute
element.addEventListener('focus', update);
// Attach keyboard event listener to trigger click event
element.addEventListener('keydown', handleKeyDown);
});
});
};

export { enhanceSortableAccessibility };
1 change: 1 addition & 0 deletions enhanceSortableAccessibility.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sortable-tablesort",
"version": "3.1.0",
"version": "3.2.0",
"description": "A tiny, Vanilla/Plain JavaScript table sorter",
"author": "Jonas Earendel",
"license": "Unlicense",
Expand All @@ -9,6 +9,8 @@
"type": "git",
"url": "git+https://github.com/tofsjonas/sortable.git"
},
"main": "sortable.min.js",
"module": "sortable.min.js",
"keywords": [
"sort",
"html",
Expand Down
28 changes: 21 additions & 7 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,38 @@ export default [
plugins: [typescript()],
},
{
input: './src/sortable.a11y.ts',
input: './src/sortable.ts',
output: {
file: './sortable.a11y.js',
file: './sortable.min.js',
},
plugins: [typescript(), terser(terser_config)],
},
{
input: './src/enhanceSortableAccessibility.ts',
output: {
file: './enhanceSortableAccessibility.js',
},
plugins: [typescript()],
},
// We will build these using closure compiler before pushing, but it's nice to have them
{
input: './src/sortable.a11y.ts',
input: './src/enhanceSortableAccessibility.ts',
output: {
file: './sortable.a11y.min.js',
file: './enhanceSortableAccessibility.min.js',
},
plugins: [typescript(), terser(terser_config)],
},
{
input: './src/sortable.ts',
input: './src/sortable.a11y.ts',
output: {
file: './sortable.min.js',
file: './sortable.a11y.js',
},
plugins: [typescript()],
},
// We will build these using closure compiler before pushing, but it's nice to have them
{
input: './src/sortable.a11y.ts',
output: {
file: './sortable.a11y.min.js',
},
plugins: [typescript(), terser(terser_config)],
},
Expand Down
1 change: 1 addition & 0 deletions sortable.a11y.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
5 changes: 4 additions & 1 deletion sortable.a11y.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var enhanceSortableAccessibility = function (tables) {
}
var aria_label = "Click to sort table by ".concat(header_text, " in ").concat(new_direction, " order");
element.setAttribute('aria-label', aria_label);
element.setAttribute('title', aria_label);
// element.setAttribute('title', aria_label) REMEMBER TO COMMENT OUT WHEN NOT TESTING!!
}
/**
* Handles keyboard events on table header cells and triggers a click event when the Enter key is pressed.
Expand All @@ -43,6 +43,9 @@ var enhanceSortableAccessibility = function (tables) {
// Iterate over each header cell in the table
headers.forEach(function (header) {
var element = header;
// Skip if the header cell already has a tabindex attribute
if (element.hasAttribute('tabindex'))
return;
var update = function () {
updateAriaLabel(element, default_direction);
};
Expand Down
4 changes: 2 additions & 2 deletions sortable.a11y.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions sortable.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* sortable v3.2.0
*
* https://www.npmjs.com/package/sortable-tablesort
* https://github.com/tofsjonas/sortable
*
* Makes html tables sortable, No longer ie9+ 😢
*
* Styling is done in css.
*
* Copyleft 2017 Jonas Earendel
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org>
*
*/
2 changes: 1 addition & 1 deletion sortable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* sortable v3.1.0
* sortable v3.2.0
*
* https://www.npmjs.com/package/sortable-tablesort
* https://github.com/tofsjonas/sortable
Expand Down
5 changes: 4 additions & 1 deletion src/enhanceSortableAccessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const enhanceSortableAccessibility = (tables: NodeListOf<HTMLTableElement
const aria_label = `Click to sort table by ${header_text} in ${new_direction} order`

element.setAttribute('aria-label', aria_label)
element.setAttribute('title', aria_label)
// element.setAttribute('title', aria_label) REMEMBER TO COMMENT OUT WHEN NOT TESTING!!
}

/**
Expand All @@ -49,6 +49,9 @@ export const enhanceSortableAccessibility = (tables: NodeListOf<HTMLTableElement
headers.forEach((header) => {
const element = header as HTMLTableCellElement

// Skip if the header cell already has a tabindex attribute
if (element.hasAttribute('tabindex')) return

const update = () => {
updateAriaLabel(element, default_direction)
}
Expand Down
4 changes: 1 addition & 3 deletions src/sortable.test.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,7 @@ <h2>Ascending sort</h2>
</tbody>
</table>



<script>
<script>
document.getElementById('morty').addEventListener('click', function (e) {
e.preventDefault()
alert('event listeners also work!')
Expand Down
72 changes: 70 additions & 2 deletions src/sortable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import '@testing-library/jest-dom'
import { JSDOM } from 'jsdom'
import fs from 'fs'
import path from 'path'

import { enhanceSortableAccessibility } from '../enhanceSortableAccessibility'
// Determine whether to use minified versions based on an environment variable
const use_minified = process.env.USE_MINIFIED === 'true'

function waitFor(conditionFunction) {
const poll = (resolve) => {
if (conditionFunction()) resolve()
else setTimeout((_) => poll(resolve), 50)
}

return new Promise(poll)
}

// Construct file paths based on the above flag
const js_filename = use_minified ? 'sortable.min.js' : 'sortable.js'
const a11y_js_filename = use_minified ? 'sortable.a11y.min.js' : 'sortable.a11y.js'
Expand Down Expand Up @@ -185,7 +194,23 @@ describe('sortable.test.html', () => {
expect(last).toBe('Rick')
})

it('sorts a table ascending using Enter key', async () => {
it('a11y labels are added and updated', async () => {
const first_string = 'Click to sort table by Name in ascending order'
const second_string = 'Click to sort table by Name in descending order'

const table = getAllByRole(container, 'table')[6]
const th = getByRole(table, 'columnheader', { name: /Name/i })
const first = th.getAttribute('aria-label')
fireEvent.click(th)
await waitFor(() => th.getAttribute('aria-label') === second_string)

const middle = th.getAttribute('aria-label')

expect(first).toBe(first_string)
expect(middle).toBe(second_string)
})

it('sorts a table using Enter key', async () => {
const table = getAllByRole(container, 'table')[6]
const th = getByRole(table, 'columnheader', { name: /Name/i })
const first = getAllByRole(table, 'cell')[1].textContent
Expand All @@ -198,4 +223,47 @@ describe('sortable.test.html', () => {
expect(middle).toBe('Morty')
expect(last).toBe('Rick')
})

it('works when using enhanceSortableAccessibility()', async () => {
const first_string = 'Click to sort table by Name in ascending order'
const second_string = 'Click to sort table by Name in descending order'

const table = getAllByRole(container, 'table')[6]
let th = getByRole(table, 'columnheader', { name: /Name/i })

// remove eventlistener from th
let clone = th.cloneNode(true)
th.parentNode.replaceChild(clone, th)

th = clone

// double check that the eventlistener is removed
// this should NOT alter the a11y label
fireEvent.click(th)

// this, however, _should_ alter the a11y label
setTimeout(() => {
// th.setAttribute('tabindex', 5)
th.setAttribute('aria-label', '')
th.removeAttribute('tabindex')
}, 100)

await waitFor(() => th.getAttribute('aria-label') === '' || th.getAttribute('aria-label') === second_string)
const middle = th.getAttribute('aria-label')
expect(middle).toBe('')

// re-enable the a11y
enhanceSortableAccessibility([table])

// wait for the a11y to be added back
await waitFor(() => th.hasAttribute('tabindex'))

fireEvent.click(th)

// see that it worked:

await waitFor(() => th.getAttribute('aria-label') === first_string)

// well, if we got here, we're good
})
})
2 changes: 1 addition & 1 deletion src/sortable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* sortable v3.1.0
* sortable v3.2.0
*
* https://www.npmjs.com/package/sortable-tablesort
* https://github.com/tofsjonas/sortable
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"sourceMap": false,
"strict": false,
"target": "ES5",
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"declaration": true
},
"compileOnSave": true,
"include": ["src/sortable.ts", "src/sortable.a11y.ts"],
Expand Down

0 comments on commit c6826cf

Please sign in to comment.