From 4bbc6c770a372ad92b67779605371cd7b7d0d433 Mon Sep 17 00:00:00 2001 From: DanyLE Date: Sat, 27 Jul 2024 22:36:17 +0200 Subject: [PATCH] feat(ListView): allows list navigation using keyboard --- Makefile | 2 +- d.ts/antos.d.ts | 70 +++++++-- src/core/BaseApplication.ts | 2 +- src/core/BaseDialog.ts | 14 +- src/core/tags/DesktopTag.ts | 2 +- src/core/tags/FileViewTag.ts | 5 + src/core/tags/ListViewTag.ts | 188 ++++++++++++++++++++++--- src/core/tags/SystemPanelTag.ts | 22 +-- src/core/tags/TabContainerTag.ts | 3 +- src/core/tags/WindowTag.ts | 2 +- src/core/tags/tag.ts | 13 ++ src/packages/Files/main.css | 5 +- src/packages/Files/main.ts | 17 ++- src/packages/Files/package.json | 2 +- src/packages/Files/scheme.html | 4 +- src/packages/Setting/package.json | 2 +- src/packages/Setting/scheme.html | 14 +- src/packages/SystemReport/package.json | 2 +- src/packages/SystemReport/scheme.html | 2 +- src/themes/default/afx-file-view.css | 3 +- src/themes/default/afx-list-view.css | 4 + src/themes/default/afx-sys-panel.css | 3 +- src/themes/system/afx-float-list.css | 4 +- src/themes/system/afx-sys-panel.css | 5 +- 24 files changed, 295 insertions(+), 95 deletions(-) diff --git a/Makefile b/Makefile index 70551e6..2c3a90f 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/d.ts/antos.d.ts b/d.ts/antos.d.ts index a81db19..d76c293 100644 --- a/d.ts/antos.d.ts +++ b/d.ts/antos.d.ts @@ -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} * @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): 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 * diff --git a/src/core/BaseApplication.ts b/src/core/BaseApplication.ts index 8fa0f42..3cbe6b7 100644 --- a/src/core/BaseApplication.ts +++ b/src/core/BaseApplication.ts @@ -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} * @memberof BaseApplication diff --git a/src/core/BaseDialog.ts b/src/core/BaseDialog.ts index 23e1bb1..dc7f4ae 100644 --- a/src/core/BaseDialog.ts +++ b/src/core/BaseDialog.ts @@ -833,7 +833,7 @@ namespace OS { SelectionDialog.scheme = `\ - +
@@ -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; - } - }); } } diff --git a/src/core/tags/DesktopTag.ts b/src/core/tags/DesktopTag.ts index 5be597d..02ee252 100644 --- a/src/core/tags/DesktopTag.ts +++ b/src/core/tags/DesktopTag.ts @@ -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 diff --git a/src/core/tags/FileViewTag.ts b/src/core/tags/FileViewTag.ts index c1acfae..2496b1c 100644 --- a/src/core/tags/FileViewTag.ts +++ b/src/core/tags/FileViewTag.ts @@ -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; diff --git a/src/core/tags/ListViewTag.ts b/src/core/tags/ListViewTag.ts index dd95b4b..23ec541 100644 --- a/src/core/tags/ListViewTag.ts +++ b/src/core/tags/ListViewTag.ts @@ -550,6 +550,15 @@ namespace OS { * @memberof ListViewTag */ private _data: GenericObject[]; + + /** + * 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,14 +1092,106 @@ 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 @@ -1099,6 +1209,56 @@ namespace OS { this._onlistdbclick(evt); 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) { diff --git a/src/core/tags/SystemPanelTag.ts b/src/core/tags/SystemPanelTag.ts index 0d55933..c3721a4 100644 --- a/src/core/tags/SystemPanelTag.ts +++ b/src/core/tags/SystemPanelTag.ts @@ -39,14 +39,6 @@ namespace OS { */ private _pending_task: Promise[]; - /** - * 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) diff --git a/src/core/tags/TabContainerTag.ts b/src/core/tags/TabContainerTag.ts index c6efa7d..f51019b 100644 --- a/src/core/tags/TabContainerTag.ts +++ b/src/core/tags/TabContainerTag.ts @@ -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 diff --git a/src/core/tags/WindowTag.ts b/src/core/tags/WindowTag.ts index e26edb0..d91a1c3 100644 --- a/src/core/tags/WindowTag.ts +++ b/src/core/tags/WindowTag.ts @@ -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(); diff --git a/src/core/tags/tag.ts b/src/core/tags/tag.ts index 3157c10..ddb7f32 100644 --- a/src/core/tags/tag.ts +++ b/src/core/tags/tag.ts @@ -532,6 +532,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"); + } + } /** * diff --git a/src/packages/Files/main.css b/src/packages/Files/main.css index f0bcb01..6272a16 100644 --- a/src/packages/Files/main.css +++ b/src/packages/Files/main.css @@ -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; diff --git a/src/packages/Files/main.ts b/src/packages/Files/main.ts index e582e14..5f8e004 100644 --- a/src/packages/Files/main.ts +++ b/src/packages/Files/main.ts @@ -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; diff --git a/src/packages/Files/package.json b/src/packages/Files/package.json index 6d801c0..2059ab4 100644 --- a/src/packages/Files/package.json +++ b/src/packages/Files/package.json @@ -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"], diff --git a/src/packages/Files/scheme.html b/src/packages/Files/scheme.html index 044ea5f..6380597 100644 --- a/src/packages/Files/scheme.html +++ b/src/packages/Files/scheme.html @@ -1,7 +1,7 @@ - + @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/src/packages/Setting/package.json b/src/packages/Setting/package.json index df23ef0..28f9bd8 100644 --- a/src/packages/Setting/package.json +++ b/src/packages/Setting/package.json @@ -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"] diff --git a/src/packages/Setting/scheme.html b/src/packages/Setting/scheme.html index 938a786..a77292c 100644 --- a/src/packages/Setting/scheme.html +++ b/src/packages/Setting/scheme.html @@ -6,7 +6,7 @@ - +
@@ -25,7 +25,7 @@ - +
@@ -45,7 +45,7 @@ - +
@@ -53,10 +53,10 @@ - +
- +
@@ -64,10 +64,10 @@ - +
- +
diff --git a/src/packages/SystemReport/package.json b/src/packages/SystemReport/package.json index 8e7a319..ef9fbea 100644 --- a/src/packages/SystemReport/package.json +++ b/src/packages/SystemReport/package.json @@ -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": [] diff --git a/src/packages/SystemReport/scheme.html b/src/packages/SystemReport/scheme.html index c5c7626..a4fffee 100644 --- a/src/packages/SystemReport/scheme.html +++ b/src/packages/SystemReport/scheme.html @@ -1,6 +1,6 @@ - +
diff --git a/src/themes/default/afx-file-view.css b/src/themes/default/afx-file-view.css index d97a4a6..82fcc1d 100644 --- a/src/themes/default/afx-file-view.css +++ b/src/themes/default/afx-file-view.css @@ -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"; diff --git a/src/themes/default/afx-list-view.css b/src/themes/default/afx-list-view.css index 0661945..c967b72 100644 --- a/src/themes/default/afx-list-view.css +++ b/src/themes/default/afx-list-view.css @@ -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); +} + diff --git a/src/themes/default/afx-sys-panel.css b/src/themes/default/afx-sys-panel.css index 97ddc96..d21a295 100644 --- a/src/themes/default/afx-sys-panel.css +++ b/src/themes/default/afx-sys-panel.css @@ -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); diff --git a/src/themes/system/afx-float-list.css b/src/themes/system/afx-float-list.css index 28a6eb8..d7777ab 100644 --- a/src/themes/system/afx-float-list.css +++ b/src/themes/system/afx-float-list.css @@ -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; diff --git a/src/themes/system/afx-sys-panel.css b/src/themes/system/afx-sys-panel.css index 27f5563..e2b0cee 100644 --- a/src/themes/system/afx-sys-panel.css +++ b/src/themes/system/afx-sys-panel.css @@ -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; } \ No newline at end of file