feat: add APIs that support responsive UI on antos tags

This commit is contained in:
DanyLE 2023-07-13 20:40:04 +02:00 committed by Dany LE
parent a5257bf108
commit e6bf4d5352
11 changed files with 393 additions and 26 deletions

View File

@ -144,6 +144,33 @@ namespace OS {
});
}
/**
* API function to register responsive UI event to the current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @param {GUI.TagResponsiveCallback} responsive callback
* @returns {void}
* @memberof BaseApplication
*/
protected morphon(validator: GUI.TagResponsiveValidator, callback: GUI.TagResponsiveCallback)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphon(validator, callback);
}
/**
* API function to unregister responsive UI event from current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @returns {void}
* @memberof BaseApplication
*/
protected morphoff(validator: GUI.TagResponsiveValidator)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphoff(validator);
}
/**
* Render the application UI by first loading its scheme
* and then mount this scheme to the DOM tree

View File

@ -701,12 +701,16 @@ namespace OS {
* @returns {HTMLElement}
* @memberof BaseModel
*/
protected find(id: string): HTMLElement {
protected find<T extends HTMLElement>(id: string): T {
if (this.scheme) {
return $(`[data-id='${id}']`, this.scheme)[0];
return $(`[data-id='${id}']`, this.scheme)[0] as T;
}
}
/*protected $(id: string) : T {
return this.find(id) as T;
}*/
/**
* Select all DOM Element inside the UI of the model
* using JQuery selector

View File

@ -101,6 +101,7 @@ namespace OS {
* @memberof FloatListTag
*/
protected mount(): void {
$(this.refs.current).hide();
$(this.refs.container)
.css("width", "100%")
.css("height", "100%");

View File

@ -1248,6 +1248,7 @@ namespace OS {
{
el: "div",
ref: "container",
class: "grid_content_container",
children: [{ el: "div", ref: "grid" }],
},
];

View File

@ -548,6 +548,9 @@ namespace OS {
*/
private _data: GenericObject<any>[];
private _drop: (any) => void;
private _show: (any) => void;
/**
* Event data passing between mouse event when performing
* drag and drop on the list
@ -577,6 +580,8 @@ namespace OS {
) => {};
this._selectedItems = [];
this._selectedItem = undefined;
this._drop = (e) => {this.dropoff(e)};
this._show = (e) => {this.showlist(e)};
}
/**
@ -617,23 +622,18 @@ namespace OS {
$(this.refs.container).removeAttr("style");
$(this.refs.mlist).removeAttr("style");
$(this).removeClass("dropdown");
const drop = (e: any) => {
return this.dropoff(e);
};
const show = (e: any) => {
return this.showlist(e);
};
if (v) {
$(this).addClass("dropdown");
$(this.refs.current).show();
$(document).on("click", drop);
$(this.refs.current).on("click", show);
$(document).on("click", this._drop);
$(this.refs.current).on("click", this._show);
$(this.refs.mlist).hide();
this.calibrate();
} else {
$(document).off("click", this._drop);
$(this.refs.current).off("click", this._show);
$(this.refs.current).hide();
$(document).off("click", drop);
$(this.refs.current).off("click", show);
$(this.refs.mlist).show();
}
}
@ -1157,10 +1157,11 @@ namespace OS {
);
}
}
// set the label content event it is hidden
const label = this.refs.drlabel as LabelTag;
label.set(e.data.data);
if (this.dropdown) {
const label = this.refs.drlabel as LabelTag;
label.set(e.data.data);
$(this.refs.mlist).hide();
}
const evt = { id: this.aid, data: edata };
@ -1350,23 +1351,37 @@ namespace OS {
}
/**
* Scroll the list view to bottom
* Scroll the list view to end
*
* @memberof ListViewTag
*/
scroll_to_bottom()
scroll_to_end()
{
this.refs.mlist.scrollTo({ top: this.refs.mlist.scrollHeight, behavior: 'smooth' })
if(this.dir == "vertical")
{
this.refs.mlist.scrollTo({ top: this.refs.mlist.scrollHeight, behavior: 'smooth' });
}
else
{
this.refs.mlist.scrollTo({ left: this.refs.mlist.scrollWidth, behavior: 'smooth' });
}
}
/**
* Scroll the list view to top
* Scroll the list view to beginning
*
* @memberof ListViewTag
*/
scroll_to_top()
scroll_to_start()
{
this.refs.mlist.scrollTo({ top: 0, behavior: 'smooth' });
if(this.dir == "vertical")
{
this.refs.mlist.scrollTo({ top: 0, behavior: 'smooth' });
}
else
{
this.refs.mlist.scrollTo({ left: 0, behavior: 'smooth' });
}
}
/**

View File

@ -136,6 +136,7 @@ namespace OS {
w: this.width,
h: this.height,
},
root: true
});
}

View File

@ -187,7 +187,10 @@ namespace OS {
? $(this).prev()[0]
: undefined;
}
if(this.dir)
{
return;
}
if (tagname === "AFX-HBOX") {
this.dir = "hz";
} else if (tagname === "AFX-VBOX") {
@ -197,6 +200,20 @@ namespace OS {
}
}
/**
* Setter Disable or enable the resize event
*
* @memberof ResizerTag
*/
set disable(v: boolean)
{
this.attsw(v, "disable");
}
get disable(): boolean
{
return this.hasattr("disable");
}
/**
* Enable draggable on the element
*
@ -210,6 +227,10 @@ namespace OS {
}
$(this).on("pointerdown", (e) => {
e.preventDefault();
if(this.disable)
{
return;
}
$(window).on("pointermove", (evt) => {
if (!this._resizable_el) {
return;

View File

@ -257,6 +257,42 @@ namespace OS {
});
}
/**
* Scroll the tabbar to end
*
* @memberof TabBarTag
*/
scroll_to_end()
{
const list_container = $(".list-container", this.refs.list)[0];
if(this.dir == "vertical")
{
list_container.scrollTo({ top: list_container.scrollHeight, behavior: 'smooth' });
}
else
{
list_container.scrollTo({ left: list_container.scrollWidth, behavior: 'smooth' });
}
}
/**
* Scroll the tabbar to begin
*
* @memberof TabBarTag
*/
scroll_to_start()
{
const list_container = $(".list-container", this.refs.list)[0];
if(this.dir == "vertical")
{
list_container.scrollTo({ top: 0, behavior: 'smooth' });
}
else
{
list_container.scrollTo({ left: 0, behavior: 'smooth' });
}
}
/**
* TabBar layout definition
*

View File

@ -1,6 +1,9 @@
namespace OS {
export namespace GUI {
export namespace tag {
type TileItemDirection = "row" | "column" | "row-reverse" | "column-reverse";
/**
* A tile layout organize it child elements
* in a fixed horizontal or vertical direction.
@ -77,15 +80,15 @@ namespace OS {
*
* @memberof TileLayoutTag
*/
set dir(v: "row" | "column") {
set dir(v: TileItemDirection) {
if (!v) {
return;
}
$(this).attr("dir", v);
$(this.refs.yield).css("flex-direction", v);
this.reversed = this.reversed;
this.calibrate();
}
get dir(): "row" | "column" {
get dir(): TileItemDirection {
return $(this).attr("dir") as any;
}
/**
@ -108,6 +111,35 @@ namespace OS {
{
return this._padding;
}
/**
* setter: Reverse order of the content in the tile
*
* getter: return if the tile's content is in reversed order
*
* @meberof TileLayoutTags
*/
set reversed(v: boolean)
{
this.attsw(v, "reversed");
if(!this.dir)
{
return;
}
let newdir = "row";
if(this.dir.startsWith("column"))
{
newdir = "column"
}
if(v)
{
newdir += "-reverse";
}
$(this.refs.yield).css("flex-direction", newdir);
}
get reversed(): boolean
{
return this.hasattr("reversed");
}
/**
* Mount the element

View File

@ -468,6 +468,7 @@ namespace OS {
this.observable.trigger("resize", {
id: this.aid,
data: o,
root: true
});
}

View File

@ -242,6 +242,13 @@ namespace OS {
* @memberof TagEventType
*/
originalEvent?: any;
/**
* is root tag?
*
* @type {boolean}
* @memberof TagEventType
*/
root?: boolean;
}
/**
@ -270,8 +277,163 @@ namespace OS {
}
/**
* Tag event callback type
*
* @export
*/
export type TagEventCallback<T> = (e: TagEventType<T>) => void;
/**
* Tag responsive envent callback type
*
* @export
*/
export type TagResponsiveCallback = (fullfilled: boolean) => void;
/**
* A callback record with history of last fulfilled
*
* @interface TagResponsiveCallbackRecord
*/
interface TagResponsiveCallbackRecord {
/**
* Callback function
*
* @type {TagResponsiveCallback}
* @memberof TagResponsiveCallbackRecord
*/
callback: TagResponsiveCallback,
/**
* has the event been previously fulfilled?
*
* @type {boolean}
* @memberof TagResponsiveCallbackRecord
*/
fulfilled: boolean,
}
/**
* Tag responsive validator type
*
* @export
*/
export type TagResponsiveValidator = (w: number, h: number) => boolean;
/**
* Definitions of some default tag responsive validators
*
* @export
*/
export const RESPONSIVE = {
/**
* Extra small devices (phones, 600px and down)
*/
TINY: function(w: number,_h: number){
return w <= 600;
},
/*
Small devices (portrait tablets and large phones, 600px and up)
*/
SMALL: function(w: number, _h: number){
return w <= 768;
},
/*
Medium devices (landscape tablets, 768px and up)
*/
MEDIUM: function(w: number, _h: number){
return w > 768;
},
/**
* Large devices (laptops/desktops, 992px and up)
*/
LARGE: function(w: number, _h: number){
return w > 992;
},
/**
* Extra large devices (large laptops and desktops, 1200px and up)
*/
HUGE: function(w: number, _h: number){
return w > 1200;
},
/**
* Portrait mode
*/
PORTRAIT: function(w: number, h: number){
return h > w;
},
/**
* Landscape mode
*/
LANDSCAPE: function(w: number, h: number){
return h < w;
},
}
/**
* A custom map to handle responsive events, a responsive event
* consists of a validator and a callback
*
* When avalidator return true, its corresponding callback will
* be called
*/
class ResponsiveHandle extends Map<TagResponsiveValidator,TagResponsiveCallbackRecord> {
/**
* Register a responsive envent to this map
*
* @param {TagResponsiveValidator} a validator
* @param {TagResponsiveCallback} event callback
* @memberof ResponsiveHandle
*/
on(validator: TagResponsiveValidator, callback: TagResponsiveCallback)
{
let record: TagResponsiveCallbackRecord = {
callback: callback,
fulfilled: false,
}
this.set(validator, record);
}
/**
* unregister a responsive event
*
* @param {TagResponsiveValidator} a validator
* @memberof ResponsiveHandle
*/
off(validator: TagResponsiveValidator)
{
this.delete(validator);
}
/**
* Verify validators and execute callbacks
*
* @param {number} root tag width
* @param {number} root tag height
* @memberof ResponsiveHandle
*/
morph(rootw: number, rooth: number)
{
for (const [validator, record] of this.entries()) {
const val = validator(rootw, rooth);
if(record.fulfilled && val)
{
// the record has been previously fulfilled
// and the validator returns true
// nothing changed
continue;
}
if(!record.fulfilled && !val)
{
// the record has been previously unfulfilled
// and the validator returns false
// nothing changed
continue;
}
record.fulfilled =val;
record.callback(val);
}
}
}
/**
* Base abstract class for tag implementation, any AFX tag should be
* subclass of this class
@ -313,7 +475,17 @@ namespace OS {
protected _mounted: boolean;
/**
*Creates an instance of AFXTag.
* a {@link ResponsiveHandle} to handle all responsive event
* related to this tag
*
* @private
* @memberof AFXTag
*/
private _responsive_handle: ResponsiveHandle;
private _responsive_check: (evt: TagEventType<{w: number, h: number}>) => void;
/**
* Creates an instance of AFXTag.
* @memberof AFXTag
*/
constructor() {
@ -324,6 +496,8 @@ namespace OS {
}
this._mounted = false;
this.refs = {};
this._responsive_handle = new ResponsiveHandle();
this._responsive_check = undefined;
}
/**
@ -394,6 +568,60 @@ namespace OS {
return $(this).attr("data-id");
}
/**
* Setter: Enable/disable responsive tag
*
* Getter: get responsive status
*/
set responsive(v:boolean) {
if(!v)
{
this.attsw(false,"responsive");
this.observable.off("resize",this._responsive_check);
this._responsive_check = undefined;
return;
}
if(this._responsive_check)
{
return;
}
this._responsive_check = (evt: TagEventType<{w: number, h: number}>) => {
if(!evt || !evt.root || !evt.data.w || !evt.data.h )
{
return;
}
this._responsive_handle.morph(evt.data.w, evt.data.h);
}
this.observable.on("resize", this._responsive_check);
this.attsw(true, "responsive");
}
get responsive(): boolean {
return this.hasattr("responsive");
}
/**
* Register a responsive event to this tag
*
* @param {TagResponsiveValidator} responsive validator
* @param {TagResponsiveCallback} responsive callback
* @memberof AFXTag
*/
morphon(validator: TagResponsiveValidator, callback:TagResponsiveCallback)
{
this._responsive_handle.on(validator, callback);
}
/**
* Unregister a responsive event from this tag
*
* @param {TagResponsiveValidator} responsive validator
* @memberof AFXTag
*/
morphoff(validator: TagResponsiveValidator)
{
this._responsive_handle.off(validator);
}
/**
* Attach a data to this tag
*