Skip to content

Commit

Permalink
Fix OXD list table highlighting issue (#738)
Browse files Browse the repository at this point in the history
* TEC-165: Fix OXD list table highlighting issue
* TEC-165: Add clickable, localization cells to OXD list table
* TEC-165: Fix focus first directive to exclude disabled inputs
  • Loading branch information
Chamara Abesinghe authored Nov 22, 2023
1 parent d91a2f5 commit 854bea7
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 20 deletions.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2023-11-21 - 35904535e09b3a0c2048300b53e0345f6f00ec85 - directives/focus-first-element - Fix focus on disabled input elements

2023-11-21 - 7bdeb5b38f87223920e3acc074b0e5b1062d38d8 - ListTable/ListTable.vue - Add new cell types, fixed duplicated key warnings

2023-11-20 - b2baf9c07983d0bdf9c772786f0dbd9b68b7172b - Icon/icons.ts - Add oxd-window-in, oxd-window-out icons

2023-11-18 - 37b5c67d67b24357d773210c952d3a899d60b008 - onKeypress changed to skip the disabled options when updating the pointer in navigation-mixin.
Expand Down
1 change: 1 addition & 0 deletions components/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ module.exports = {
moduleNameMapper: {
'^!!raw-loader!(.*)$': '$1',
},
setupFiles: ['<rootDir>/jest.init.js'],
};
5 changes: 5 additions & 0 deletions components/jest.init.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {config} from '@vue/test-utils';

config.global.mocks = {
$t: text => text,
};
108 changes: 108 additions & 0 deletions components/src/composables/__tests__/useFlashing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {defineComponent} from 'vue';
import {mount} from '@vue/test-utils';
import useFlashing, {getDiff} from '../useFlashing';

const TestComponent = defineComponent({
props: ['loading', 'flashing', 'items', 'order'],
setup(props, context) {
return {
flashIndexes: useFlashing(props, context),
};
},
template: `<pre>{{flashIndexes}}</pre>`,
});

describe('useFlashing composable', () => {
it('useFlashing::getDiff should detect when item added to array', () => {
const original = [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
];

const updated = [...original, {id: 4, name: 'Banana'}];
expect(getDiff(updated, original)).toStrictEqual([3]);
});
it('useFlashing::getDiff should detect when item updated in array', () => {
const original = [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
];

const updated = JSON.parse(JSON.stringify(original));
updated[2].name = 'Guava';
expect(getDiff(updated, original)).toStrictEqual([2]);
});
it('useFlashing::getDiff should not detect when items reordered in array', () => {
const original = [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
];

const updated = [...original].reverse();
expect(getDiff(updated, original)).toStrictEqual([]);
});
it('useFlashing::getDiff should not detect when items removed in array', () => {
const original = [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
];

const updated = [...original];
updated.pop();
expect(getDiff(updated, original)).toStrictEqual([]);
});

it('useFlashing should detect changes', async () => {
const wrapper = mount(TestComponent, {
props: {
flashing: true,
},
});
await wrapper.setProps({
items: [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
],
});
expect(wrapper.vm.flashIndexes).toStrictEqual([]);
await wrapper.setProps({
items: [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
{id: 4, name: 'Guava'},
],
});
expect(wrapper.vm.flashIndexes).toStrictEqual([3]);
});

it('useFlashing should not detect changes if flashing disabled', async () => {
const wrapper = mount(TestComponent, {
props: {
flashing: false,
},
});
await wrapper.setProps({
items: [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
],
});
expect(wrapper.vm.flashIndexes).toStrictEqual([]);
await wrapper.setProps({
items: [
{id: 1, name: 'Orange'},
{id: 2, name: 'Apple'},
{id: 3, name: 'Cherry'},
{id: 4, name: 'Guava'},
],
});
expect(wrapper.vm.flashIndexes).toStrictEqual([]);
});
});
32 changes: 16 additions & 16 deletions components/src/composables/useFlashing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ type RowItem = {
[name: string]: string | number | object | null;
};

export const getDiff = (newValue: RowItem[], oldValue: RowItem[]) => {
const diff: number[] = [];
for (let i = 0; i < newValue.length; i++) {
const isNew =
oldValue.findIndex(item => {
const o = JSON.stringify(Object.entries(item).sort());
const n = JSON.stringify(Object.entries(newValue[i]).sort());
return o === n;
}) === -1;
if (isNew) diff.push(i);
}
return diff;
};

export default function useFlashing(
props: ShallowReactive<{
order: object;
Expand All @@ -13,9 +27,9 @@ export default function useFlashing(
}>,
context: SetupContext,
) {
const flashIndexes = shallowRef<number[]>([]);
let flash = false;
let cachedItems: RowItem[] = JSON.parse(JSON.stringify(props.items));
let cachedItems: RowItem[] = [];
const flashIndexes = shallowRef<number[]>([]);

// Reset cached values if loading or sorting
watch(
Expand All @@ -31,20 +45,6 @@ export default function useFlashing(
},
);

const getDiff = (newValue: RowItem[], oldValue: RowItem[]) => {
const diff: number[] = [];
for (let i = 0; i < newValue.length; i++) {
const isNew =
oldValue.findIndex(item => {
const o = JSON.stringify(Object.entries(item).sort());
const n = JSON.stringify(Object.entries(newValue[i]).sort());
return o === n;
}) === -1;
if (isNew) diff.push(i);
}
return diff;
};

if (props.flashing) {
watch(
() => props.items,
Expand Down
5 changes: 5 additions & 0 deletions components/src/core/components/ListTable/CellContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
resolveComponent,
} from 'vue';
import DefaultCell from './Default.vue';
import ClickableCell from './ClickableCell.vue';
import LocalizedCell from './LocalizedCell.vue';
import {CardHeaders} from '../CardTable/types';
import {RowItem} from '../CardTable/Cell/types';
import LinkCell from '@orangehrm/oxd/core/components/CardTable/Cell/Link.vue';
Expand All @@ -27,6 +29,8 @@ export default defineComponent({
'oxd-table-cell-default': DefaultCell,
'oxd-table-cell-actions': ActionsCell,
'oxd-table-cell-checkbox': CheckboxCell,
'oxd-table-cell-clickable': ClickableCell,
'oxd-table-cell-localized': LocalizedCell,
'oxd-table-cell-profile-pic': ProfilePicCell,
'oxd-table-cell-link-with-pill': LinkWithPillCell,
},
Expand Down Expand Up @@ -84,6 +88,7 @@ export default defineComponent({
item: cellData,
header: header,
rowItem: rowData,
class: header.class,
loading: this.loading,
},
header.cellProps ?? {},
Expand Down
77 changes: 77 additions & 0 deletions components/src/core/components/ListTable/ClickableCell.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div :class="classes" @click.stop="onClick">
<oxd-skeleton v-if="loading" animate></oxd-skeleton>
<template v-else>{{ item }}</template>
</div>
</template>

<script lang="ts">
import {defineComponent} from 'vue';
import Skeleton from '@orangehrm/oxd/core/components/Skeleton/Skeleton.vue';
export default defineComponent({
name: 'oxd-table-cell-clickable',
components: {
'oxd-skeleton': Skeleton,
},
props: {
header: {
type: Object,
default: () => ({}),
},
rowItem: {
type: Object,
default: () => ({}),
},
item: {
type: [Number, String, Object],
default: () => null,
},
loading: {
type: Boolean,
default: false,
},
},
computed: {
isDisabled(): boolean {
const isRowDisabled =
this.rowItem?.isDisabled === undefined
? false
: Boolean(this.rowItem.isDisabled);
const isTableDisabled =
this.tableProps?.disabled === undefined
? false
: Boolean(this.tableProps.disabled);
return isTableDisabled ? isTableDisabled : isRowDisabled;
},
classes(): object {
return {
'oxd-table-card-cell': true,
'--clickable': !this.isDisabled && !this.loading,
};
},
},
methods: {
onClick(e: MouseEvent) {
if (this.isDisabled) return;
const cellConfig = this.header?.cellConfig;
if (cellConfig && typeof cellConfig?.onClick === 'function') {
this.header.cellConfig.onClick(this.rowItem, e);
}
},
},
});
</script>

<style lang="scss" scoped>
.oxd-table-card-cell {
&.--clickable {
cursor: pointer;
width: fit-content;
}
}
</style>
1 change: 1 addition & 0 deletions components/src/core/components/ListTable/ListTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ export default defineComponent({
_skeleton: true,
};
props.headers.forEach(header => {
defaultValue['_id'] = nanoid();
defaultValue[header.name] = null;
});
Expand Down
41 changes: 41 additions & 0 deletions components/src/core/components/ListTable/LocalizedCell.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<div class="oxd-table-card-cell">
<oxd-skeleton v-if="loading" animate></oxd-skeleton>
<template v-else>{{ $vt(item) }}</template>
</div>
</template>

<script lang="ts">
import {defineComponent} from 'vue';
import translateMixin from '../../../mixins/translate';
import Skeleton from '@orangehrm/oxd/core/components/Skeleton/Skeleton.vue';
export default defineComponent({
name: 'oxd-table-cell-localized',
components: {
'oxd-skeleton': Skeleton,
},
mixins: [translateMixin],
props: {
header: {
type: Object,
default: () => ({}),
},
rowItem: {
type: Object,
default: () => ({}),
},
item: {
type: [Number, String],
default: () => null,
},
loading: {
type: Boolean,
default: false,
},
},
});
</script>
3 changes: 2 additions & 1 deletion components/src/directives/focus-first-element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export interface FocusFirstHTMLElement extends HTMLElement {
activeElement?: Element | null;
}

const focusableElements = 'input, select, textarea, [tabindex], [href]';
const focusableElements =
'input:not([disabled]), select, textarea, [tabindex], [href]';
const excludeElements =
'button:not(.oxd-dialog-close-button,.modal-reset-button,[disabled])';
const firstFocusedElementsOnMounted = new Map<string | null, Element>();
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4696,9 +4696,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001286:
version "1.0.30001486"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz"
integrity sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==
version "1.0.30001563"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001563.tgz"
integrity sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==

capture-exit@^2.0.0:
version "2.0.0"
Expand Down

0 comments on commit 854bea7

Please sign in to comment.