1
1
import { isDocumentFragmentOrElementNode } from '../utils/dom.ts' ;
2
2
3
- type DirElement = HTMLInputElement | HTMLTextAreaElement ;
3
+ let globalSelectorObserverInited = false ;
4
4
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 > > = { } ;
15
10
16
11
type GlobalInitFunc < T extends HTMLElement > = ( el : T ) => void | Promise < void > ;
17
12
const 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
- }
24
13
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.
27
15
export function registerGlobalEventFunc < T extends HTMLElement , E extends Event > ( event : string , name : string , func : GlobalEventFunc < T , E > ) {
28
16
globalEventFuncs [ `${ event } :${ name } ` ] = func as any ;
29
17
}
30
18
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 ) {
42
22
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 ) ) {
45
27
handler ( el ) ;
46
28
}
47
29
}
48
30
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 } ) ;
50
69
const observer = new MutationObserver ( ( mutationList ) => {
51
70
const len = mutationList . length ;
52
71
for ( let i = 0 ; i < len ; i ++ ) {
@@ -60,30 +79,17 @@ export function initAddedElementObserver(): void {
60
79
if ( addedNode . matches ( selector ) ) {
61
80
handler ( addedNode ) ;
62
81
}
63
- const children = addedNode . querySelectorAll < HTMLElement > ( selector ) ;
64
- for ( const el of children ) {
82
+ for ( const el of addedNode . querySelectorAll < HTMLElement > ( selector ) ) {
65
83
handler ( el ) ;
66
84
}
67
85
}
68
86
}
69
87
}
70
88
} ) ;
71
-
72
89
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 ) ) {
75
91
handler ( el ) ;
76
92
}
77
93
}
78
-
79
94
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
- } ) ;
89
95
}
0 commit comments