11import { isDocumentFragmentOrElementNode } from '../utils/dom.ts' ;
22
3- type DirElement = HTMLInputElement | HTMLTextAreaElement ;
3+ let globalSelectorObserverInited = false ;
44
5- // for performance considerations, it only uses performant syntax
6- function attachDirAuto ( el : Partial < DirElement > ) {
7- if ( el . type !== 'hidden' &&
8- el . type !== 'checkbox' &&
9- el . type !== 'radio' &&
10- el . type !== 'range' &&
11- el . type !== 'color' ) {
12- el . dir = 'auto' ;
13- }
14- }
5+ type SelectorHandler = { selector : string , handler : ( el : HTMLElement ) => void } ;
6+ const selectorHandlers : SelectorHandler [ ] = [ ] ;
7+
8+ type GlobalEventFunc < T extends HTMLElement , E extends Event > = ( el : T , e : E ) => ( void | Promise < void > ) ;
9+ const globalEventFuncs : Record < string , GlobalEventFunc < HTMLElement , Event > > = { } ;
1510
1611type GlobalInitFunc < T extends HTMLElement > = ( el : T ) => void | Promise < void > ;
1712const globalInitFuncs : Record < string , GlobalInitFunc < HTMLElement > > = { } ;
18- function attachGlobalInit ( el : HTMLElement ) {
19- const initFunc = el . getAttribute ( 'data-global-init' ) ;
20- const func = globalInitFuncs [ initFunc ] ;
21- if ( ! func ) throw new Error ( `Global init function "${ initFunc } " not found` ) ;
22- func ( el ) ;
23- }
2413
25- type GlobalEventFunc < T extends HTMLElement , E extends Event > = ( el : T , e : E ) => ( void | Promise < void > ) ;
26- const globalEventFuncs : Record < string , GlobalEventFunc < HTMLElement , Event > > = { } ;
14+ // It handles the global events for all `<div data-global-click="onSomeElemClick"></div>` elements.
2715export function registerGlobalEventFunc < T extends HTMLElement , E extends Event > ( event : string , name : string , func : GlobalEventFunc < T , E > ) {
2816 globalEventFuncs [ `${ event } :${ name } ` ] = func as any ;
2917}
3018
31- type SelectorHandler = {
32- selector : string ,
33- handler : ( el : HTMLElement ) => void ,
34- } ;
35-
36- const selectorHandlers : SelectorHandler [ ] = [
37- { selector : 'input, textarea' , handler : attachDirAuto } ,
38- { selector : '[data-global-init]' , handler : attachGlobalInit } ,
39- ] ;
40-
41- export function observeAddedElement ( selector : string , handler : ( el : HTMLElement ) => void ) {
19+ // It handles the global init functions by a selector, for example:
20+ // > registerGlobalSelectorObserver('.ui.dropdown:not(.custom)', (el) => { initDropdown(el, ...) });
21+ export function registerGlobalSelectorFunc ( selector : string , handler : ( el : HTMLElement ) => void ) {
4222 selectorHandlers . push ( { selector, handler} ) ;
43- const docNodes = document . querySelectorAll < HTMLElement > ( selector ) ;
44- for ( const el of docNodes ) {
23+ // Then initAddedElementObserver will call this handler for all existing elements after all handlers are added.
24+ // This approach makes the init stage only need to do one "querySelectorAll".
25+ if ( ! globalSelectorObserverInited ) return ;
26+ for ( const el of document . querySelectorAll < HTMLElement > ( selector ) ) {
4527 handler ( el ) ;
4628 }
4729}
4830
49- export function initAddedElementObserver ( ) : void {
31+ // It handles the global init functions for all `<div data-global-int="initSomeElem"></div>` elements.
32+ export function registerGlobalInitFunc < T extends HTMLElement > ( name : string , handler : GlobalInitFunc < T > ) {
33+ globalInitFuncs [ name ] = handler as any ;
34+ // The "global init" functions are managed internally and called by callGlobalInitFunc
35+ // They must be ready before initGlobalSelectorObserver is called.
36+ if ( globalSelectorObserverInited ) throw new Error ( 'registerGlobalInitFunc() must be called before initGlobalSelectorObserver()' ) ;
37+ }
38+
39+ function callGlobalInitFunc ( el : HTMLElement ) {
40+ const initFunc = el . getAttribute ( 'data-global-init' ) ;
41+ const func = globalInitFuncs [ initFunc ] ;
42+ if ( ! func ) throw new Error ( `Global init function "${ initFunc } " not found` ) ;
43+
44+ type GiteaGlobalInitElement = Partial < HTMLElement > & { _giteaGlobalInited : boolean } ;
45+ if ( ( el as GiteaGlobalInitElement ) . _giteaGlobalInited ) throw new Error ( `Global init function "${ initFunc } " already executed` ) ;
46+ ( el as GiteaGlobalInitElement ) . _giteaGlobalInited = true ;
47+ func ( el ) ;
48+ }
49+
50+ function attachGlobalEvents ( ) {
51+ // add global "[data-global-click]" event handler
52+ document . addEventListener ( 'click' , ( e ) => {
53+ const elem = ( e . target as HTMLElement ) . closest < HTMLElement > ( '[data-global-click]' ) ;
54+ if ( ! elem ) return ;
55+ const funcName = elem . getAttribute ( 'data-global-click' ) ;
56+ const func = globalEventFuncs [ `click:${ funcName } ` ] ;
57+ if ( ! func ) throw new Error ( `Global event function "click:${ funcName } " not found` ) ;
58+ func ( elem , e ) ;
59+ } ) ;
60+ }
61+
62+ export function initGlobalSelectorObserver ( ) : void {
63+ if ( globalSelectorObserverInited ) throw new Error ( 'initGlobalSelectorObserver() already called' ) ;
64+ globalSelectorObserverInited = true ;
65+
66+ attachGlobalEvents ( ) ;
67+
68+ selectorHandlers . push ( { selector : '[data-global-init]' , handler : callGlobalInitFunc } ) ;
5069 const observer = new MutationObserver ( ( mutationList ) => {
5170 const len = mutationList . length ;
5271 for ( let i = 0 ; i < len ; i ++ ) {
@@ -60,30 +79,17 @@ export function initAddedElementObserver(): void {
6079 if ( addedNode . matches ( selector ) ) {
6180 handler ( addedNode ) ;
6281 }
63- const children = addedNode . querySelectorAll < HTMLElement > ( selector ) ;
64- for ( const el of children ) {
82+ for ( const el of addedNode . querySelectorAll < HTMLElement > ( selector ) ) {
6583 handler ( el ) ;
6684 }
6785 }
6886 }
6987 }
7088 } ) ;
71-
7289 for ( const { selector, handler} of selectorHandlers ) {
73- const docNodes = document . querySelectorAll < HTMLElement > ( selector ) ;
74- for ( const el of docNodes ) {
90+ for ( const el of document . querySelectorAll < HTMLElement > ( selector ) ) {
7591 handler ( el ) ;
7692 }
7793 }
78-
7994 observer . observe ( document , { subtree : true , childList : true } ) ;
80-
81- document . addEventListener ( 'click' , ( e ) => {
82- const elem = ( e . target as HTMLElement ) . closest < HTMLElement > ( '[data-global-click]' ) ;
83- if ( ! elem ) return ;
84- const funcName = elem . getAttribute ( 'data-global-click' ) ;
85- const func = globalEventFuncs [ `click:${ funcName } ` ] ;
86- if ( ! func ) throw new Error ( `Global event function "click:${ funcName } " not found` ) ;
87- func ( elem , e ) ;
88- } ) ;
8995}
0 commit comments