Skip to content

Commit

Permalink
feat: improving navManager (#183)
Browse files Browse the repository at this point in the history
  • Loading branch information
divdavem authored Nov 15, 2023
1 parent 8a35c33 commit 4e9153c
Show file tree
Hide file tree
Showing 21 changed files with 1,203 additions and 57 deletions.
65 changes: 65 additions & 0 deletions angular/demo/src/app/samples/navmanager/navmanager.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type {NavManagerItemConfig} from '@agnos-ui/angular';
import {AgnosUIAngularModule, createNavManager} from '@agnos-ui/angular';
import {Component, Input} from '@angular/core';

@Component({
standalone: true,
selector: 'app-navmanager-line',
imports: [AgnosUIAngularModule],
template: `
<div class="d-flex demo-navmanager-line">
<input [auUse]="navManager.directive" [auUseParams]="navManagerConfig" type="text" [value]="text" class="form-control me-1" />
<span [auUse]="navManager.directive" [auUseParams]="navManagerConfig" tabindex="-1" class="form-control w-auto me-1">{{ text }}</span>
<input
[auUse]="navManager.directive"
[auUseParams]="navManagerConfig"
tabindex="-1"
type="checkbox"
class="form-check-input align-self-center me-1"
/>
<input
[auUse]="navManager.directive"
[auUseParams]="navManagerConfig"
tabindex="-1"
type="text"
[value]="text"
disabled
class="form-control me-1"
/>
<input [auUse]="navManager.directive" [auUseParams]="navManagerConfig" tabindex="-1" type="text" [value]="text" class="form-control me-1" />
</div>
`,
})
export class NavmanagerLineComponent {
@Input() text = '';

navManager = createNavManager();

navManagerConfig: NavManagerItemConfig = {
keys: {
ArrowLeft: this.navManager.focusLeft,
ArrowRight: this.navManager.focusRight,
Home: this.navManager.focusFirst,
End: this.navManager.focusLast,
},
};
}

@Component({
standalone: true,
imports: [NavmanagerLineComponent],
template: `
<div class="demo-navmanager">
<div dir="ltr" class="mt-3 pb-3">
<h2>Left-to-right</h2>
<app-navmanager-line text="Hello" />
</div>
<div dir="rtl" class="mt-3 pb-3">
<h2>Right-to-left</h2>
<app-navmanager-line text="שָׁלוֹם" />
</div>
</div>
`,
})
export default class NavmanagerComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type {NavManagerItemConfig} from '@agnos-ui/angular';
import {AgnosUIAngularModule, createNavManager} from '@agnos-ui/angular';
import {Component, Input} from '@angular/core';

@Component({
standalone: true,
selector: 'app-navmanager-line',
imports: [AgnosUIAngularModule],
template: `
<div class="d-flex demo-navmanager-line" [auUse]="navManager.directive" [auUseParams]="navManagerConfig">
<input type="text" [value]="text" class="form-control me-1" />
<span tabindex="-1" class="form-control w-auto me-1">{{ text }}</span>
<input tabindex="-1" type="checkbox" class="form-check-input align-self-center me-1" />
<input tabindex="-1" type="text" [value]="text" disabled class="form-control me-1" />
<input tabindex="-1" type="text" [value]="text" class="form-control me-1" />
</div>
`,
})
export class NavmanagerLineComponent {
@Input() text = '';

navManager = createNavManager();

navManagerConfig: NavManagerItemConfig = {
keys: {
ArrowLeft: this.navManager.focusLeft,
ArrowRight: this.navManager.focusRight,
Home: this.navManager.focusFirst,
End: this.navManager.focusLast,
},
selector: (divElement) => divElement.querySelectorAll('input,span'),
};
}

@Component({
standalone: true,
imports: [NavmanagerLineComponent],
template: `
<div class="demo-navmanager">
<div dir="ltr" class="mt-3 pb-3">
<h2>Left-to-right</h2>
<app-navmanager-line text="Hello" />
</div>
<div dir="rtl" class="mt-3 pb-3">
<h2>Right-to-left</h2>
<app-navmanager-line text="שָׁלוֹם" />
</div>
</div>
`,
})
export default class NavmanagerComponent {}
2 changes: 1 addition & 1 deletion angular/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"importHelpers": true,
"target": "ES2022",
"module": "es2020",
"lib": ["es2020", "dom"],
"lib": ["es2020", "dom", "dom.iterable"],
"useDefineForClassFields": false,
"paths": {
"@agnos-ui/core": ["./core/lib"],
Expand Down
43 changes: 43 additions & 0 deletions core/lib/services/domUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {beforeEach, describe, expect, test} from 'vitest';
import {computeCommonAncestor} from './domUtils';

describe('computeCommonAncestor', () => {
let parentElement: HTMLElement;
beforeEach(() => {
parentElement = document.body.appendChild(document.createElement('div'));
return () => {
parentElement.parentElement?.removeChild(parentElement);
};
});

test('Basic functionalities', () => {
parentElement.innerHTML = `
<div id="element1">
<div id="element2"></div>
<div id="element3"></div>
</div>
`;
const element1 = document.getElementById('element1')!;
const element2 = document.getElementById('element2')!;
const element3 = document.getElementById('element3')!;
const element4 = document.createElement('div');
expect(computeCommonAncestor([])).toBe(null);
expect(computeCommonAncestor([element1])).toBe(element1);
expect(computeCommonAncestor([element1, element1])).toBe(element1);
expect(computeCommonAncestor([element1, element1, element1])).toBe(element1);
expect(computeCommonAncestor([element1, element2])).toBe(element1);
expect(computeCommonAncestor([element2, element1])).toBe(element1);
expect(computeCommonAncestor([element1, element3])).toBe(element1);
expect(computeCommonAncestor([element3, element1])).toBe(element1);
expect(computeCommonAncestor([element2, element3])).toBe(element1);
expect(computeCommonAncestor([element3, element2])).toBe(element1);
expect(computeCommonAncestor([element1, element2, element3])).toBe(element1);
expect(computeCommonAncestor([element2, element1, element3])).toBe(element1);
expect(computeCommonAncestor([element2, element3, element1])).toBe(element1);
expect(computeCommonAncestor([element3, element2, element1])).toBe(element1);
expect(computeCommonAncestor([element3, element1, element2])).toBe(element1);
expect(computeCommonAncestor([element4])).toBe(element4);
expect(computeCommonAncestor([element1, element4])).toBe(null);
expect(computeCommonAncestor([element4, element1])).toBe(null);
});
});
29 changes: 29 additions & 0 deletions core/lib/services/domUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Returns the common ancestor of the provided DOM elements.
* @param elements - array of DOM elements
* @returns the common ancestor, or null if the array is empty or if there is no common ancestor (e.g.: if elements are detached)
*/
export const computeCommonAncestor = (elements: HTMLElement[]) => {
const length = elements.length;
if (length === 0) return null;
let ancestor: HTMLElement | null = elements[0];
for (let i = 1; i < length && ancestor; i++) {
const element = elements[i];
while (ancestor) {
if (ancestor === element) {
break;
}
const comparison = ancestor.compareDocumentPosition(element);
if (comparison & Node.DOCUMENT_POSITION_CONTAINED_BY) {
break;
} else if (comparison & Node.DOCUMENT_POSITION_CONTAINS) {
ancestor = element;
break;
} else if (comparison & Node.DOCUMENT_POSITION_DISCONNECTED) {
return null;
}
ancestor = ancestor.parentElement;
}
}
return ancestor;
};
1 change: 1 addition & 0 deletions core/lib/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './stores';
export * from './writables';
export * from './navManager';
export * from './isFocusable';
export * from './domUtils';
Loading

0 comments on commit 4e9153c

Please sign in to comment.