feat(ListView): allows list navigation using keyboard

This commit is contained in:
DanyLE 2024-07-27 22:36:17 +02:00
parent bfeef223a8
commit 4bbc6c770a
24 changed files with 295 additions and 95 deletions

View File

@ -8,7 +8,7 @@ TSC=./node_modules/typescript/bin/tsc
UGLIFYJS=./node_modules/terser/bin/terser
UGLIFYCSS=./node_modules/uglifycss/uglifycss
VERSION?=2.0.1-b
VERSION?=2.0.2-b
BUILDID?=master
GSED=sed

70
d.ts/antos.d.ts vendored
View File

@ -2337,7 +2337,7 @@ declare namespace OS {
* in JSON format as .settings.json and will be loaded automatically
* when application is initialized.
*
* This object is globally acessible to all processes of the same application
* This object is globally accessible to all processes of the same application
*
* @type {GenericObject<any>}
* @memberof BaseApplication
@ -3927,6 +3927,12 @@ declare namespace OS {
* @memberof AFXTag
*/
set tooltip(v: string);
/**
* Setter to activate or deactivate focus on a Tag
*
* @memberof AFXTag
*/
set focusable(v: boolean);
/**
*
* This function looking for a property name of the tag
@ -4501,6 +4507,14 @@ declare namespace OS {
* @memberof ListViewTag
*/
private _data;
/**
* Navigation index, used for keyboard navigation only
*
* @private
* @type {number}
* @memberof ListViewTag
*/
private _nav_index;
private _drop;
private _show;
/**
@ -4753,6 +4767,43 @@ declare namespace OS {
* @memberof ListViewTag
*/
private iclick;
/**
* Reset the navigation indicator
*
* @private
*/
private nav_reset;
/**
* Navigate the list up
*
* @public
* @returns {void}
* @memberof ListViewTag
*/
nav_next(): void;
/**
* Navigate the list down
*
* @returns {void}
* @memberof ListViewTag
*/
nav_prev(): void;
/**
* Commit the navigated item
*
* @returns {void}
* @memberof ListViewTag
*/
nav_commit(): void;
/**
* Handle special key event such as key up and down
*
* @private
* @param {JQuery.KeyboardEventBase} event
* @returns {void}
* @memberof ListViewTag
*/
private handle_special_key;
/**
* This function triggers the double click event on an item
*
@ -4762,6 +4813,15 @@ declare namespace OS {
* @memberof ListViewTag
*/
protected idbclick(e: TagEventType<ListViewItemTag>): void;
/**
* This function scroll to item if it is not visible
*
* @public
* @param {ListViewItemTag} item tag event object
* @returns
* @memberof ListViewTag
*/
scroll_to_item(item: ListViewItemTag): void;
/**
* This function triggers the list item select event
*
@ -6557,14 +6617,6 @@ declare namespace OS {
* @memberof SystemPanelTag
*/
private _pending_task;
/**
* Flag indicate where the selected application shall be opened
*
* @private
* @type {boolean}
* @memberof SystemPanelTag
*/
private _prevent_open;
/**
* Store the current attached service
*

View File

@ -40,7 +40,7 @@ namespace OS {
* in JSON format as .settings.json and will be loaded automatically
* when application is initialized.
*
* This object is globally acessible to all processes of the same application
* This object is globally accessible to all processes of the same application
*
* @type {GenericObject<any>}
* @memberof BaseApplication

View File

@ -833,7 +833,7 @@ namespace OS {
SelectionDialog.scheme = `\
<afx-app-window width='350' height='300' apptitle = "Selection">
<afx-vbox padding = "10">
<afx-list-view data-id = "list" ></afx-list-view>
<afx-list-view data-id = "list" focusable="true"></afx-list-view>
<div data-height="35" style = "text-align: right;">
<afx-button data-id = "btnOk" text = "__(Ok)" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
@ -1114,18 +1114,6 @@ namespace OS {
if (this.data && this.data.hidden) {
return (fileview.showhidden = this.data.hidden);
}
$(this.scheme).on("keyup", (evt)=>{
if(evt.which === 38)
{
const currdir = fileview.path.asFileHandle();
if (currdir.isRoot()) {
return;
}
const p = currdir.parent();
return fileview.path = p.path;
}
});
}
}

View File

@ -65,7 +65,7 @@ namespace OS {
* TRICKY HACK
* When focusing on a window which overflows the desktop,
* the desktop scrolls automatically to bottom,
* even when `overflow: hiddle` is set on CSS.
* even when `overflow: hidden` is set on CSS.
*
* The following event listener prevents
* the desktop to scroll down in this case

View File

@ -733,6 +733,11 @@ namespace OS {
};
const grid = this.refs.gridview as GridViewTag;
const list = this.refs.listview as ListViewTag;
list.focusable = true;
grid.focusable = true;
tree.focusable = true;
grid.resizable = true;
grid.header = this._header;
tree.dragndrop = true;

View File

@ -551,6 +551,15 @@ namespace OS {
*/
private _data: GenericObject<any>[];
/**
* Navigation index, used for keyboard navigation only
*
* @private
* @type {number}
* @memberof ListViewTag
*/
private _nav_index: number;
private _drop: (any) => void;
private _show: (any) => void;
@ -601,6 +610,7 @@ namespace OS {
this.dragndrop = false;
this._anchor = undefined;
this.itemtag = "afx-list-item";
this._nav_index = -1;
$(this).addClass("afx-list-view");
}
@ -624,6 +634,7 @@ namespace OS {
this.attsw(v, "dropdown");
$(this.refs.container).removeAttr("style");
$(this.refs.mlist).removeAttr("style");
this.dir = "column";
$(this).removeClass("dropdown");
if (v) {
$(this).addClass("dropdown");
@ -799,7 +810,11 @@ namespace OS {
this.calibrate();
}
get dir(): string {
return $(this).attr("dir");
const v = $(this).attr("dir");
if(!v) {
return "column";
}
return v;
}
/**
* Getter: Get data of the list
@ -861,14 +876,17 @@ namespace OS {
const el = data[i].domel as ListViewItemTag;
el.selected = true;
};
this.nav_reset();
if (Array.isArray(idx)) {
if (this.multiselect) {
for (const i of idx as number[]) {
select(i);
this._nav_index = i;
}
}
} else {
select(idx as number);
this._nav_index = idx;
}
}
@ -1074,15 +1092,107 @@ namespace OS {
if (!e.data) {
return;
}
this.nav_reset();
this._nav_index = -1;
const list = this.selectedItems;
if (this.multiselect && list.includes(e.data) && !flag) {
list.splice(list.indexOf(e.data), 1);
e.data.selected = false;
return;
}
this._nav_index = $(e.data).index();
e.data.selected = true;
}
/**
* Reset the navigation indicator
*
* @private
*/
private nav_reset() {
if(this._nav_index >= 0 && this._nav_index < this.data.length)
{
$(this.data[this._nav_index].domel as ListViewItemTag).removeClass("listview_nav_focus");
}
}
/**
* Navigate the list up
*
* @public
* @returns {void}
* @memberof ListViewTag
*/
public nav_next() {
if(this._nav_index <= 0) {
return;
}
this.nav_reset();
this._nav_index--;
const new_el = this.data[this._nav_index].domel as ListViewItemTag;
if(new_el) {
$(new_el).addClass("listview_nav_focus");
this.scroll_to_item(new_el);
}
}
/**
* Navigate the list down
*
* @returns {void}
* @memberof ListViewTag
*/
public nav_prev() {
if(this._nav_index >= this.data.length - 1) {
return;
}
this.nav_reset();
this._nav_index++;
const new_el = this.data[this._nav_index].domel as ListViewItemTag;
if(new_el) {
$(new_el).addClass("listview_nav_focus");
this.scroll_to_item(new_el);
}
}
/**
* Commit the navigated item
*
* @returns {void}
* @memberof ListViewTag
*/
public nav_commit() {
if(this._nav_index > this.data.length - 1 || this._nav_index < 0) {
return;
}
this.selected = this._nav_index;
}
/**
* Handle special key event such as key up and down
*
* @private
* @param {JQuery.KeyboardEventBase} event
* @returns {void}
* @memberof ListViewTag
*/
private handle_special_key(event: JQuery.KeyboardEventBase) {
switch (event.which) {
case 37:
case 38:
this.nav_next();
return event.preventDefault();
case 39:
case 40:
this.nav_prev();
return event.preventDefault();
case 13:
event.preventDefault();
return this.nav_commit();
default:
break;
}
}
/**
* This function triggers the double click event on an item
*
@ -1100,6 +1210,56 @@ namespace OS {
return this.observable.trigger("listdbclick", evt);
}
/**
* This function scroll to item if it is not visible
*
* @public
* @param {ListViewItemTag} item tag event object
* @returns
* @memberof ListViewTag
*/
public scroll_to_item(item: ListViewItemTag) {
const li = $(item).children()[0];
const offset = $(this.refs.container).offset();
const li_offset = $(li).offset();
if(this.dir == "column") {
const top = $(this.refs.container).scrollTop();
const li_height = $(li).outerHeight();
const container_height = $(this.refs.container).outerHeight();
if (li_offset.top + li_height > container_height + offset.top)
{
$(this.refs.container).scrollTop(
top +
(li_offset.top + li_height - offset.top - container_height)
);
} else if ($(li).offset().top < offset.top) {
$(this.refs.container).scrollTop(
top -
(offset.top - li_offset.top)
);
}
}
else
{
const left = $(this.refs.container).scrollLeft();
const li_w = $(li).outerWidth();
const container_w = $(this.refs.container).outerWidth();
if (li_offset.left + li_w > offset.left + container_w)
{
$(this.refs.container).scrollLeft(
left +
(li_offset.left + li_w - offset.left - container_w)
);
} else if ($(li).offset().left < offset.left) {
$(this.refs.container).scrollLeft(
left -
(offset.left - li_offset.left)
);
}
}
}
/**
* This function triggers the list item select event
*
@ -1141,25 +1301,7 @@ namespace OS {
this._selectedItems = [e.data];
edata.items = [e.data];
//scroll element
const li = $(e.data).children()[0];
const offset = $(this.refs.container).offset();
const top = $(this.refs.container).scrollTop();
if (
$(li).offset().top + $(li).height() >
$(this.refs.container).height() + offset.top
) {
$(this.refs.container).scrollTop(
top +
$(this.refs.container).height() -
$(li).height()
);
} else if ($(li).offset().top < offset.top) {
$(this.refs.container).scrollTop(
top -
$(this.refs.container).height() +
$(li).height()
);
}
this.scroll_to_item(e.data);
}
// set the label content event it is hidden
@ -1185,6 +1327,12 @@ namespace OS {
from: undefined,
to: undefined,
};
$(this).on("keyup", (e) => {
this.handle_special_key(e);
});
//$(this.refs.search).on("click",(e) => {
// console.log("focus")
//})
this._onmousedown = (e) => {
if(this.multiselect || this.selectedItems == undefined || this.selectedItems.length == 0)
{

View File

@ -39,14 +39,6 @@ namespace OS {
*/
private _pending_task: Promise<any>[];
/**
* Flag indicate where the selected application shall be opened
*
* @private
* @type {boolean}
* @memberof SystemPanelTag
*/
private _prevent_open: boolean;
/**
* Store the current attached service
*
@ -96,7 +88,6 @@ namespace OS {
this._loading_toh = undefined;
this.app_list= [];
this._services = [];
this._prevent_open = false;
}
/**
@ -137,11 +128,6 @@ namespace OS {
* @memberof SystemPanelTag
*/
private open(): void {
if(this._prevent_open)
{
this._prevent_open = false;
return;
}
const applist = this.refs.applist as ListViewTag;
const el = applist.selectedItem;
if (!el) {
@ -170,20 +156,18 @@ namespace OS {
return this.toggle(false);
case 37:
this._prevent_open = true;
applist.selectPrev();
applist.nav_next();
return e.preventDefault();
case 38:
return e.preventDefault();
case 39:
this._prevent_open = true;
applist.selectNext();
applist.nav_prev();
return e.preventDefault();
case 40:
return e.preventDefault();
case 13:
e.preventDefault();
return this.open();
return applist.nav_commit();
default:
catlist.selected = 0;
var text = (this.refs.search as HTMLInputElement)

View File

@ -169,7 +169,7 @@ namespace OS {
}
$(v.container).show();
this.observable.trigger("resize", undefined);
$(v.container).attr("tabindex",-1).css("outline", "none").trigger("focus");
$(v.container).trigger("focus");
}
get selectedTab(): TabContainerTabType {
return this._selectedTab;
@ -230,6 +230,7 @@ namespace OS {
$(item.container)
.css("width", "100%")
.css("height", "100%")
.attr("tabindex",-1).css("outline", "none") // allow focus this tab
.hide();
const el = (this.refs.bar as TabBarTag).push(
item

View File

@ -436,7 +436,7 @@ namespace OS {
w: this.width,
h: this.height,
});
$(this).attr("tabindex", 0).css("outline", "none");
this.focusable = true
if(OS.mobile)
{
this.toggle_window();

View File

@ -533,6 +533,19 @@ namespace OS {
$(this).attr("tooltip", v);
}
/**
* Setter to activate or deactivate focus on a Tag
*
* @memberof AFXTag
*/
set focusable(v: boolean) {
if(v) {
$(this).attr("tabindex", 0).css("outline", "none");
} else {
$(this).removeAttr("tabindex");
}
}
/**
*
* This function looking for a property name of the tag

View File

@ -10,14 +10,17 @@ afx-app-window[data-id ='files-app-window'] afx-file-view afx-list-view i:before
text-align: center;
}
/*
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected {
background-color: var(--background-quaternary) !important;
color:var(--text-secondary) !important;
}
*/
/*
afx-app-window[data-id ='files-app-window'] afx-file-view {
background-color: var(--background-tertiary);
}
*/
afx-app-window[data-id ='files-app-window'] button{
border-radius: 0;

View File

@ -433,6 +433,14 @@ namespace OS {
this.bindKey("CTRL-ALT-R", ()=>{
this.view.path = this.currdir.path;
});
this.bindKey("CTRL-B", () => {
if (this.currdir.isRoot()) {
return;
}
const p = this.currdir.parent();
this.favo.selected = -1;
return this.view.path = p.path;
});
(this.find("btgrid") as GUI.tag.ButtonTag).onbtclick = (e) => {
this.view.view = "icon";
this.viewType.icon = true;
@ -454,15 +462,6 @@ namespace OS {
}
});
$(this.scheme).on("keyup", (evt)=>{
if(evt.which === 38)
{
if (this.currdir.isRoot()) {
return;
}
const p = this.currdir.parent();
this.favo.selected = -1;
return this.view.path = p.path;
}
if(!evt.ctrlKey)
{
this.view.multiselect = false;

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com"
},
"version":"0.1.10-b",
"version":"0.1.11-b",
"category":"System",
"iconclass":"fa fa-hdd-o",
"mimes":["dir"],

View File

@ -1,7 +1,7 @@
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="500" responsive="true">
<afx-tile data-id="container" name="vbox" dir="column">
<afx-tile data-height = "35" min-width="120" data-width="180" data-id = "nav-bar" name="hbox" dir="row">
<afx-list-view data-id = "favouri" dropdown="true"></afx-list-view>
<afx-list-view data-id = "favouri" dropdown="true" focusable="true"></afx-list-view>
</afx-tile>
<afx-resizer data-id="resizer" dir="column" disable="true" data-width="3" data-height="0"></afx-resizer>
<afx-vbox>

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com"
},
"version":"0.2.0-b",
"version":"0.2.1-b",
"category":"System",
"iconclass":"fa fa-wrench",
"mimes":["none"]

View File

@ -6,7 +6,7 @@
<afx-vbox>
<afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="30"></afx-label>
<afx-hbox>
<afx-list-view data-width="150" data-id="wplist"></afx-list-view>
<afx-list-view data-width="150" data-id="wplist" focusable="true"></afx-list-view>
<afx-resizer data-width="2"></afx-resizer>
<div data-id = "wp-preview"></div>
</afx-hbox>
@ -25,7 +25,7 @@
<afx-hbox data-id="vfs" tabname = "__(VFS)" iconclass = "fa fa-inbox" padding="10">
<afx-vbox>
<afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="mplist"></afx-list-view>
<afx-list-view data-id="mplist" focusable="true"></afx-list-view>
<afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="30"></afx-label>
<afx-hbox data-height = "40" >
<div data-width="16"></div>
@ -45,7 +45,7 @@
<afx-hbox data-id="locale" tabname = "__(Languages)"iconclass = "fa fa-globe" padding="10">
<afx-vbox>
<afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="lglist"></afx-list-view>
<afx-list-view data-id="lglist" focusable="true"></afx-list-view>
<div data-height="10"></div>
</afx-vbox>
</afx-hbox>
@ -53,10 +53,10 @@
<afx-hbox data-id="startup" tabname = "__(Startup)" iconclass = "fa fa-cog" padding="10">
<afx-vbox>
<afx-label text = "__(Startup services)" iconclass = "fa fa-tasks" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="srvlist"></afx-list-view>
<afx-list-view data-id="srvlist" focusable="true"></afx-list-view>
<div data-height="5"></div>
<afx-label text = "__(Startup applications)" iconclass = "fa fa-adn" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="applist"></afx-list-view>
<afx-list-view data-id="applist" focusable="true"></afx-list-view>
<div data-height="10"></div>
</afx-vbox>
</afx-hbox>
@ -64,10 +64,10 @@
<afx-hbox data-id="app-services" tabname = "__(Apps. and Services)" iconclass = "fa fa-adn" padding="10">
<afx-vbox>
<afx-label text = "__(Services)" iconclass = "fa fa-tasks" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="sys-srvlist"></afx-list-view>
<afx-list-view data-id="sys-srvlist" focusable="true"></afx-list-view>
<div data-height="5"></div>
<afx-label text = "__(Pinned applications)" iconclass = "fa fa-adn" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="sys-applist"></afx-list-view>
<afx-list-view data-id="sys-applist" focusable="true"></afx-list-view>
<div data-height="10"></div>
</afx-vbox>
</afx-hbox>

View File

@ -8,7 +8,7 @@
"email": "xsang.le@gmail.com",
"licences": "GPLv3"
},
"version": "0.1.4-b",
"version": "0.1.5-b",
"category": "System",
"iconclass": "fa fa-bug",
"mimes": []

View File

@ -1,6 +1,6 @@
<afx-app-window data-id="SystemReport" width='600' height='450' apptitle = "__(System error log)" responsive="true">
<afx-hbox>
<afx-list-view data-id = "loglist" data-width="200"> </afx-list-view>
<afx-list-view data-id = "loglist" data-width="200" focusable="true"> </afx-list-view>
<afx-resizer data-width = "2" ></afx-resizer>
<afx-vbox>
<div data-id = "container">

View File

@ -6,9 +6,10 @@ afx-file-view afx-label.status{
afx-file-view afx-list-view > div.list-container > ul li{
background-color: transparent;
}
/*
afx-file-view afx-list-view > div.list-container > ul .afx-list-item:nth-child(even) li {
background-color: transparent;
}
}*/
afx-file-view afx-list-view i.dir:before{
content: "\f07b";
font-family: "FontAwesome";

View File

@ -65,3 +65,7 @@ afx-list-view .afx-list-item li afx-label.description
padding-left: 10px;
}
afx-list-view > div.list-container > ul > .listview_nav_focus > li{
background-color: var(--item-bg-hover);
}

View File

@ -49,7 +49,8 @@ afx-sys-panel afx-hbox[data-id="btlist"] afx-button button
font-size: 24px;
}
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul .afx-list-item li:hover
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul .afx-list-item li:hover,
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul .listview_nav_focus > li
{
background-color: var(--antos-ant-color)/*#cecece*/;
color: var(--text-tertiary);

View File

@ -1,11 +1,11 @@
afx-desktop div.list-container > ul, afx-float-list div.list-container > ul{
afx-desktop > div.list-container > ul, afx-float-list > div.list-container > ul{
padding: 0;
margin: 0;
background-color: transparent;
}
afx-desktop div.list-container > ul li, afx-float-list div.list-container > ul li{
afx-desktop > div.list-container > ul li, afx-float-list > div.list-container > ul li{
padding: 0;
margin: 0;
background-color: transparent;

View File

@ -121,10 +121,11 @@ afx-sys-panel afx-list-view[data-id="applist"] afx-label span
{
flex-direction: column;
}
/*
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li:hover {
background-color: transparent;
}
afx-sys-panel afx-list-view[data-id="applist"]> div.list-container > ul .afx-list-item:nth-child(even) li
}*/
afx-sys-panel afx-list-view[data-id="applist"]> div.list-container > ul .afx-list-item li
{
background-color: transparent;
}