ucbuilder
Version:
:Shree Ganeshay Namah: new way app design
474 lines • 19.9 kB
JavaScript
import { ucUtil } from "../global/ucUtil.js";
import { WinManager } from "./WinManager.js";
class ErrorSound {
static audioCtx = new (window.AudioContext || window.webkitAudioContext)();
static gainNode = (() => {
const gain = ErrorSound.audioCtx.createGain();
gain.connect(ErrorSound.audioCtx.destination);
return gain;
})();
static playErrorBeep() {
if (ErrorSound.audioCtx.state === 'suspended') {
ErrorSound.audioCtx.resume();
}
const osc = ErrorSound.audioCtx.createOscillator();
const gain = ErrorSound.audioCtx.createGain();
osc.type = 'square'; // Sharp, attention-catching
osc.frequency.value = 140; // Low pitch
gain.gain.setValueAtTime(0.35, ErrorSound.audioCtx.currentTime);
// Optional fade-out (smooth ending)
gain.gain.exponentialRampToValueAtTime(0.001, ErrorSound.audioCtx.currentTime + 0.12);
osc.connect(gain);
gain.connect(ErrorSound.gainNode);
osc.start(ErrorSound.audioCtx.currentTime);
osc.stop(ErrorSound.audioCtx.currentTime + 0.15); // ~150ms
osc.onended = () => {
osc.disconnect();
gain.disconnect();
};
}
}
class TabIndexManager {
static beep() { ErrorSound.playErrorBeep(); }
static stopFurther(e, breakTheLoop = false) {
if (e == undefined)
return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (breakTheLoop)
TabIndexManager.breakTheLoop = true;
}
mainHT;
static _music = false;
static get music() {
return TabIndexManager._music;
}
static set music(value) {
TabIndexManager._music = value;
}
static _breakTheLoop = false;
static get breakTheLoop() {
return this._breakTheLoop;
}
static set breakTheLoop(value) {
this._breakTheLoop = value;
}
static continueusMove = async (container, { startAt = undefined, stopAt = undefined }) => {
TabIndexManager.breakTheLoop = false;
let callback = {
target: container, callback: async () => { TabIndexManager.breakTheLoop = true; return true; }
};
this.Events.onContainerBottomLeave.push(callback);
let activeElement = startAt == undefined ? container : startAt;
let prevActEle = undefined;
do {
await this.moveNext(activeElement, undefined);
if (TabIndexManager.breakTheLoop) {
this.beep();
break;
}
if (activeElement === document.activeElement)
TabIndexManager.breakTheLoop = true;
activeElement = document.activeElement;
if (activeElement === stopAt || prevActEle == activeElement)
TabIndexManager.breakTheLoop = true;
prevActEle = activeElement;
} while (!TabIndexManager.breakTheLoop);
TabIndexManager.breakTheLoop = false;
this.Events.onContainerBottomLeave["#RemoveMultiple"](callback);
};
constructor() { }
static Events = {
onContainerTopLeave: [],
onContainerTopEnter: [],
onContainerBottomLeave: [],
onContainerBottomEnter: [],
//onContainerClear:new CommonEvent<(element:HTMLElement)=>{}>()
};
static status = 'none';
static isInited = false;
static init( /*mainHT: HTMLElement*/) {
/*this.mainHT = mainHT;*/
if (this.isInited)
return;
this.isInited = true;
let htEle;
let tIndex;
// this.gainNode.connect(this.audioCtx.destination);
let keyDownTimer = null;
let keyIsDown = false;
WinManager.event.keyup.on(async (ev) => {
});
WinManager.event.keydown.on(async (ev) => {
//console.log(ev.code);
if (ev.defaultPrevented) {
ev.stopImmediatePropagation();
ev.preventDefault();
ev.stopPropagation();
return;
}
let _EVENT_target = ev.target;
let _EVENT_keyCode = ev.code;
let _EVENT_shiftKey = ev.shiftKey;
let code = _EVENT_keyCode;
switch (code) {
case 'Backspace':
let constructorName = Object.getPrototypeOf(_EVENT_target).constructor.name;
switch (constructorName) {
case HTMLTextAreaElement.name:
case HTMLInputElement.name:
let ele = _EVENT_target;
let _val = ele.value;
switch (ele.type) {
case undefined: // HTMLTextAreaElement
case "text":
case "password":
case "email":
case "search":
case "tel":
case "url":
case "number":
case "date":
case "datetime-local":
case "month":
case "week":
case "time":
if (_val == '' || _val == ucUtil._getSelectedValuee(ele)) {
await this.movePrev(_EVENT_target, ev);
this.status = 'none';
ev.preventDefault();
}
break;
default:
await this.movePrev(_EVENT_target, ev);
this.status = 'none';
ev.preventDefault();
break;
}
break;
default:
await this.movePrev(_EVENT_target, ev);
this.status = 'none';
ev.preventDefault();
break;
}
break;
case 'Enter':
case 'NumpadEnter':
if (Object.getPrototypeOf(_EVENT_target).constructor.name == HTMLTextAreaElement.name) {
let ele = _EVENT_target;
let _val = ele.value;
if (_val != '' && _val == ucUtil._getSelectedValuee(ele)) {
}
else {
if (!_val.endsWith('\n'))
break;
else
ele.value = _val.slice(0, -1);
}
}
case 'Tab':
if (!_EVENT_shiftKey) {
await this.moveNext(_EVENT_target, ev);
}
else {
await this.movePrev(_EVENT_target, ev);
}
this.status = 'none';
ev.preventDefault();
break;
case 'ArrowLeft':
htEle = _EVENT_target;
tIndex = this.getTindex(htEle);
if (tIndex != null) {
if (!this.FOCUSABLE_ELEMENTS.includes(htEle.nodeName) && htEle.contentEditable != "true") {
await this.movePrev(htEle, ev);
ev.preventDefault();
}
}
break;
case 'ArrowRight':
htEle = _EVENT_target;
tIndex = this.getTindex(htEle);
if (tIndex != null) {
if (!this.FOCUSABLE_ELEMENTS.includes(htEle.nodeName) && htEle.contentEditable != "true") {
await this.moveNext(htEle, ev);
ev.preventDefault();
}
}
break;
}
this.breakTheLoop = false;
});
}
static movePrev = async (target, ev, goAhead = false) => {
let _this = this;
this.status = 'backword';
let tIndex = parseInt(target.getAttribute('x-tabindex'));
if (tIndex == null)
return;
if (goAhead) {
tIndex--;
if (tIndex == -1) { // IF REACHED TO 0 TAB INDEX;
let parent = this.getDirectParent(target);
if (await this._HELLO_KON(parent, ev, this.Events.onContainerTopLeave))
return;
/* let evt = this.Events.onContainerTopLeave;
let _obj = evt.find(s => s.target == parent);
if (_obj != undefined) if (_obj.callback() === true) return;*/
await this.movePrev(parent, ev, true);
}
else if (tIndex < -1) { // IF IN THE AIR ,-)
}
else {
let parent = this.getDirectParent(target);
let ele = this.getDirectElement(parent, tIndex) ?? this.getAnyPreviousBefore(parent, tIndex);
if (this.isFocusableElement(ele)) { // IF PREVIOUS ELEMENT IS TEXTBOX
this.focusTo(ele);
}
else {
if (ele !== undefined)
await this.movePrev(ele, ev);
else {
//let ele = this.getDirectParent(parent);
if ((await this._HELLO_KON(ele, ev, this.Events.onContainerTopLeave)))
return;
// let evt = this.Events.onContainerTopLeave;
// let _obj = evt.find(s => s.target == parent);
// if (_obj != undefined) if (_obj.callback(ev) === true) return;
await this.movePrev(parent, ev, true);
}
}
}
}
else {
let childLastElement = this.getLastElement(target);
if (childLastElement == undefined) { // IF NO CHILD TAB INDEX AVALABLE
await this.movePrev(target, ev, true);
}
else { // IF CHILD TAB INDEX EXIST
if ((await this._HELLO_KON(target, ev, this.Events.onContainerBottomEnter)))
return;
/*let evt = this.Events.onContainerBottomEnter;
let _obj = evt.find(s => s.target == target);
if (_obj != undefined) if (_obj.callback() === true) return;*/
if (this.isFocusableElement(childLastElement)) { // IF LAST ELEMENT IS TEXTBOX
this.focusTo(childLastElement);
}
else { // MOVE TO PREVIOUS WITH CHECK
await this.movePrev(childLastElement, ev);
}
}
}
};
static getLastElement(container, index = -Infinity) {
/* let i = 0;
let lastEle: HTMLElement;
do {
let ele = this.getDirectElement(container, i);
if (ele == undefined) break;
lastEle = ele;
i++;
} while (true);
return lastEle; */
let maxEl = undefined;
let maxValue = index;
const elements = container.querySelectorAll('[x-tabindex]');
for (const el of elements) {
const nearestTabParent = el.parentElement?.closest('[x-tabindex]');
if (nearestTabParent === container) {
const val = Number(el.getAttribute('x-tabindex'));
if (!Number.isNaN(val) && val > maxValue) {
maxValue = val;
maxEl = el;
}
}
}
return maxEl;
}
static getFirstElement(container, index = Infinity) {
let minEl = undefined;
let minValue = index;
const elements = container.querySelectorAll('[x-tabindex]');
for (const el of elements) {
const nearestTabParent = el.parentElement?.closest('[x-tabindex]');
if (nearestTabParent === container) {
const val = Number(el.getAttribute('x-tabindex'));
if (!Number.isNaN(val) && val < minValue) {
minValue = val;
minEl = el;
}
}
}
return minEl;
}
static getAnyNextAfter(container, currentIndex) {
let nextEl = undefined;
let nextValue = Infinity;
const elements = container.querySelectorAll('[x-tabindex]');
for (const el of elements) {
const nearestTabParent = el.parentElement?.closest('[x-tabindex]');
if (nearestTabParent === container) {
const val = Number(el.getAttribute('x-tabindex'));
if (!Number.isNaN(val) && val > currentIndex && val < nextValue) {
nextValue = val;
nextEl = el;
}
}
}
return nextEl;
}
static getAnyPreviousBefore(container, currentIndex) {
let prevEl = undefined;
let prevValue = -Infinity;
const elements = container.querySelectorAll('[x-tabindex]');
for (const el of elements) {
const nearestTabParent = el.parentElement?.closest('[x-tabindex]');
if (nearestTabParent === container) {
const val = Number(el.getAttribute('x-tabindex'));
if (!Number.isNaN(val) && val < currentIndex && val > prevValue) {
prevValue = val;
prevEl = el;
}
}
}
return prevEl;
}
static SKIP_CONTAINER_EVENTS = false;
static _HELLO_KON = async (ele, e, cnt) => {
let res = false;
if (TabIndexManager.breakTheLoop || TabIndexManager.SKIP_CONTAINER_EVENTS)
return true;
for (let i = 0, len = cnt.length; i < len; i++) {
let nd = cnt[i];
if (nd.target != undefined && ele == nd.target) {
//TabIndexManager.breakTheLoop = true;
res = await nd.callback(e) === true;
break;
}
}
return res;
};
static moveNext = async (target, ev, goAhead = false) => {
let _this = this;
//debugger;
this.status = 'forward';
if (!target.isConnected)
return;
let tIndex = parseInt(target.getAttribute('x-tabindex'));
if (tIndex == null)
return;
if (!this.isVisaulyAppeared(target))
goAhead = true;
if (goAhead && (await this._HELLO_KON(target, ev, this.Events.onContainerBottomLeave)))
return;
// if (this._HELLO_KON(target, this.Events.onContainerTopEnter)) return; // <------ REMOVE THIS CODE IF ANY BUG
let childFirstElement = goAhead ? undefined : this.getDirectElement(target, 0) ?? this.getAnyNextAfter(target, 0);
if (childFirstElement != undefined) { // IF FIRST CHILD TAB-INDEX EXIST
if ((await this._HELLO_KON(childFirstElement, ev, this.Events.onContainerTopEnter)))
return;
/*let evt = this.Events.onContainerTopEnter;
let _obj = evt.find(s => s.target == childFirstElement);
if (_obj != undefined) if (_obj.callback() === true) return;*/
if (this.isFocusableElement(childFirstElement)) { // IF FIRST CHILD IS TEXTBOX
this.focusTo(childFirstElement);
}
else { // GO TO NEXT TAB-INDEX
await this.moveNext(childFirstElement, ev);
}
}
else { // GO TO NEXT TAB-INDEX
tIndex++;
let parent = this.getDirectParent(target);
if (parent == null)
return;
// debugger;
let ele = this.getDirectElement(parent, tIndex) ?? this.getAnyNextAfter(parent, tIndex);
if ((await this._HELLO_KON(ele, ev, this.Events.onContainerTopEnter)))
return;
if (this.isFocusableElement(ele)) { // IF NEXT ELEMENT IS TEXTBOX
this.focusTo(ele);
}
else { // IF NEXT ELEMENT HAS CHILD ELEMENT
if (ele != undefined) { // IF NEXT ELEMENT EXIST
await this.moveNext(ele, ev);
}
else { // ELSE
//if (this._HELLO_KON(parent, this.Events.onContainerBottomLeave)) return;
/* let evt = this.Events.onContainerBottomLeave;
let _obj = evt.find(s => s.target == parent);
if (_obj != undefined) if (_obj.callback() === true) return;*/
await this.moveNext(parent, ev, true); // GO TO PARENT CONTAINER AND MOVE NEXT TAB-INDEX
}
}
}
};
static getDirectParent(element) { return this.getClosest(element); }
static getDirectElement(container, index) {
let ar = Array.from(container.querySelectorAll(`[x-tabindex="${index}"]`));
ar = ar.filter(s => container === this.getClosest(s));
return ar[0];
}
static focusTo(htele) {
this.status = 'none';
if (this.breakTheLoop) {
if (this.music)
this.beep();
this.music = this.breakTheLoop = this.SKIP_CONTAINER_EVENTS = false;
return;
}
htele.focus();
ucUtil.selectAllText(htele);
}
static getTindex(target) {
if (target == undefined)
return null;
let tIndex = target.getAttribute('x-tabindex');
return (tIndex == null) ? null : parseInt(tIndex);
}
static getClosest(target) {
//if (target.parentElement == undefined) debugger;
return target.parentElement?.closest("[x-tabindex]");
}
static FOCUSABLE_ELEMENTS = ["A",
"BUTTON", "INPUT", "SELECT", "TEXTAREA", /*"OPTION", "OPTGROUP", "FIELDSET",
"SUMMARY", "IFRAME", "AREA", "AUDIO", "VIDEO", "EMBED", "OBJECT"*/
];
static isVisaulyAppeared(hte) {
if (hte == undefined)
return false;
let isVisible = true;
isVisible = ucUtil.currentStyles(hte).visibility == 'visible';
return hte.offsetWidth > 0 && hte.offsetHeight > 0 && isVisible && hte.closest('[inert]') == null; //;;!hte.hasAttribute('inert');
}
static isFocusableElement(hte) {
let isVisaulyAppeared = this.isVisaulyAppeared(hte);
if (!isVisaulyAppeared)
return false;
let element = hte;
let isPrimaryInputElements = (this.FOCUSABLE_ELEMENTS.includes(element.nodeName) /*!= null*/ ||
element.contentEditable == 'true');
if (!isPrimaryInputElements) {
if (element.hasAttribute('x-tabstop')) {
const tabStop = element.getAttribute('x-tabstop');
if (tabStop === `true` || tabStop == '1') {
element.tabIndex = -1;
isPrimaryInputElements = true;
}
}
}
return !element.disabled && isPrimaryInputElements;
}
static isDirectClose(child, container) {
return container == child.parentElement.closest(`[x-tabindex]`);
}
}
const elementState = Object.freeze({
Undefined: 0,
Editable: 1,
Disabled: 2,
Container: 3,
});
export { TabIndexManager };
//# sourceMappingURL=TabIndexManager.js.map