UNPKG

ucbuilder

Version:

:Shree Ganeshay Namah: new way app design

474 lines 19.9 kB
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