@@ -8,6 +8,33 @@ function convertStringToDomNodes(htmlString) {
88 return Array . from ( fakeParent . content . childNodes ) ;
99}
1010
11+ function resolveReactive ( arg , callback ) {
12+ if ( arg ?. isObservable ) return arg . subscribe ( callback ) ;
13+ if ( typeof arg === "function" ) return Computed ( arg ) . subscribe ( callback ) ;
14+ callback ( arg ) ;
15+ return null ;
16+ }
17+
18+ function isInsideTag ( str ) {
19+ let inTag = false ;
20+ let quote = null ;
21+ for ( let j = 0 ; j < str . length ; j ++ ) {
22+ const ch = str [ j ] ;
23+ if ( quote ) {
24+ if ( ch === quote ) quote = null ;
25+ } else if ( inTag ) {
26+ if ( ch === '"' || ch === "'" ) {
27+ quote = ch ;
28+ } else if ( ch === '>' ) {
29+ inTag = false ;
30+ }
31+ } else if ( ch === '<' ) {
32+ inTag = true ;
33+ }
34+ }
35+ return inTag ;
36+ }
37+
1138function isArrayOfSupportedValues ( value ) {
1239 return Array . isArray ( value )
1340 && value . every ( ( item ) => [ "string" , "number" , "boolean" ] . includes ( typeof item ) || item instanceof HTMLElement || item instanceof Node ) ;
@@ -28,7 +55,7 @@ function replaceStringOrHTMLElement(placeholderNode, value) {
2855 if ( item instanceof HTMLElement || item instanceof Node ) {
2956 domNodes . push ( item ) ;
3057 } else {
31- domNodes . push ( ... convertStringToDomNodes ( item ) ) ;
58+ domNodes . push ( document . createTextNode ( String ( item ) ) ) ;
3259 }
3360 }
3461 placeholderNode . replaceWith ( ...domNodes ) ;
@@ -37,39 +64,23 @@ function replaceStringOrHTMLElement(placeholderNode, value) {
3764 placeholderNode . replaceWith ( value ) ;
3865 return [ value ] ;
3966 } else if ( [ "string" , "number" , "boolean" ] . includes ( typeof value ) ) {
40- const domNodes = convertStringToDomNodes ( value ) ;
41- placeholderNode . replaceWith ( ... domNodes ) ;
42- return domNodes ;
67+ const textNode = document . createTextNode ( String ( value ) ) ;
68+ placeholderNode . replaceWith ( textNode ) ;
69+ return [ textNode ] ;
4370 } else {
4471 throw new Error ( "Unsupported type for template placeholder: " + value ) ;
4572 }
4673}
4774
4875function replacePlaceholderNode ( placeholderNode , arg ) {
49- if ( arg ?. isObservable ) {
50- let elements = [ placeholderNode ] ;
51- arg . subscribe ( ( value ) => {
52- for ( let i = 1 ; i < elements . length ; i ++ ) {
53- const element = elements [ i ] ;
54- element . remove ( ) ;
55- }
56- elements = replaceStringOrHTMLElement ( elements [ 0 ] , value ) ;
57- } ) ;
58- return ;
59- }
60- if ( typeof arg === "function" ) {
61- const computed = Computed ( arg ) ;
62- let elements = [ placeholderNode ] ;
63- computed . subscribe ( ( value ) => {
64- for ( let i = 1 ; i < elements . length ; i ++ ) {
65- const element = elements [ i ] ;
66- element . remove ( ) ;
67- }
68- elements = replaceStringOrHTMLElement ( elements [ 0 ] , value ) ;
69- } ) ;
70- return ;
71- }
72- replaceStringOrHTMLElement ( placeholderNode , arg ) ;
76+ let elements = [ placeholderNode ] ;
77+ return resolveReactive ( arg , ( value ) => {
78+ for ( let i = 1 ; i < elements . length ; i ++ ) {
79+ const element = elements [ i ] ;
80+ element . remove ( ) ;
81+ }
82+ elements = replaceStringOrHTMLElement ( elements [ 0 ] , value ) ;
83+ } ) ;
7384}
7485
7586function makePlaceholderId ( i , instanceId ) {
@@ -108,27 +119,12 @@ function findNodeByAttributeKey(fakeParent, attributeKey) {
108119}
109120
110121function handleClassList ( node , arg , placeholder ) {
111- if ( arg ?. isObservable ) {
112- let currentClass = placeholder ;
113- arg . subscribe ( ( value ) => {
114- if ( currentClass ) node . classList . remove ( currentClass ) ;
115- if ( value ) node . classList . add ( value ) ;
116- currentClass = value ;
117- } ) ;
118- return ;
119- }
120- if ( typeof arg === "function" ) {
121- const computed = Computed ( arg ) ;
122- let currentClass = placeholder ;
123- computed . subscribe ( ( value ) => {
124- if ( currentClass ) node . classList . remove ( currentClass ) ;
125- if ( value ) node . classList . add ( value ) ;
126- currentClass = value ;
127- } ) ;
128- return ;
129- }
130- if ( arg ) node . classList . add ( arg ) ;
131- node . classList . remove ( placeholder ) ;
122+ let currentClass = placeholder ;
123+ return resolveReactive ( arg , ( value ) => {
124+ if ( currentClass ) node . classList . remove ( currentClass ) ;
125+ if ( value ) node . classList . add ( value ) ;
126+ currentClass = value ;
127+ } ) ;
132128}
133129
134130// this handles the case that the whole attribute including key and value is
@@ -137,8 +133,16 @@ function handleClassList(node, arg, placeholder) {
137133function handleDynamicAttribute ( node , arg , placeholder ) {
138134
139135 function update ( val ) {
140- let [ key , value ] = val . split ( "=" , 2 ) ;
141- value = ( value ?? "''" ) . slice ( 1 , - 1 ) ;
136+ const eqIndex = val . indexOf ( "=" ) ;
137+ let key , value ;
138+ if ( eqIndex === - 1 ) {
139+ key = val ;
140+ value = "" ;
141+ } else {
142+ key = val . slice ( 0 , eqIndex ) ;
143+ value = val . slice ( eqIndex + 1 ) ;
144+ value = value . slice ( 1 , - 1 ) ;
145+ }
142146 const lastKey = node . getAttribute ( placeholder ) ;
143147 if ( lastKey && node . hasAttribute ( lastKey ) ) {
144148 node . removeAttribute ( lastKey ) ;
@@ -149,18 +153,7 @@ function handleDynamicAttribute(node, arg, placeholder) {
149153 }
150154 }
151155
152- if ( arg ?. isObservable ) {
153- arg . subscribe ( ( value ) => update ( value ) ) ;
154- return ;
155- }
156-
157- if ( typeof arg === "function" ) {
158- const computed = Computed ( arg ) ;
159- computed . subscribe ( ( value ) => update ( value ) ) ;
160- return ;
161- }
162-
163- update ( arg ) ;
156+ return resolveReactive ( arg , ( value ) => update ( value ) ) ;
164157}
165158
166159function handleIfAttribute ( node , attributeKey , arg ) {
@@ -198,44 +191,25 @@ function handleIfAttribute(node, attributeKey, arg) {
198191 }
199192 }
200193
201- if ( arg ?. isObservable ) {
202- arg . subscribe ( ( value ) => update ( value ) ) ;
203- return ;
204- }
205-
206- if ( typeof arg === "function" ) {
207- const computed = Computed ( arg ) ;
208- computed . subscribe ( ( value ) => update ( value ) ) ;
209- return ;
210- }
211-
212- update ( arg ) ;
194+ return resolveReactive ( arg , ( value ) => update ( value ) ) ;
213195}
214196
215197function replaceAttributePlaceholder ( node , attributeKey , arg , placeholder ) {
216198
217199 // If no attribute key is given, the whole attribute will be replaced
218200 if ( ! attributeKey ) {
219- handleDynamicAttribute ( node , arg , placeholder ) ;
220- return ;
221- }
222-
223- if ( attributeKey === "if" ) {
224- handleIfAttribute ( node , attributeKey , arg ) ;
225- return ;
201+ return handleDynamicAttribute ( node , arg , placeholder ) ;
226202 }
227203
228- if ( attributeKey === "if-not" ) {
229- handleIfAttribute ( node , attributeKey , arg ) ;
230- return ;
204+ if ( attributeKey === "if" || attributeKey === "if-not" ) {
205+ return handleIfAttribute ( node , attributeKey , arg ) ;
231206 }
232207
233208 if ( attributeKey === "class" ) {
234209 if ( ! node . classList . contains ( placeholder ) ) {
235210 throw new Error ( "Fatal: Could not find placeholder in class attribute: " + placeholder ) ;
236211 }
237- handleClassList ( node , arg , placeholder ) ;
238- return ;
212+ return handleClassList ( node , arg , placeholder ) ;
239213 }
240214
241215 if ( attributeKey . startsWith ( "on" ) ) {
@@ -244,31 +218,32 @@ function replaceAttributePlaceholder(node, attributeKey, arg, placeholder) {
244218 }
245219 node . addEventListener ( attributeKey . slice ( 2 ) , arg ) ;
246220 node . removeAttribute ( attributeKey ) ;
247- return ;
221+ return null ;
248222 }
249223 const [ before , after ] = node . getAttribute ( attributeKey ) . split ( placeholder ) ;
250224
251225 if ( arg ?. isObservable ) {
252226 if ( attributeKey === "value" ) {
253227 node . addEventListener ( "input" , ( event ) => arg . value = event . target . value ) ;
254228 }
255- arg . subscribe ( ( value ) => {
229+ const unsub = arg . subscribe ( ( value ) => {
256230 setNodeAttribute ( node , attributeKey , before + value + after ) ;
257231 placeholder = value ;
258232 } ) ;
259- return ;
233+ return unsub ;
260234 }
261235
262236 if ( typeof arg === "function" ) {
263237 const computed = Computed ( arg ) ;
264- computed . subscribe ( ( value ) => {
238+ const unsub = computed . subscribe ( ( value ) => {
265239 setNodeAttribute ( node , attributeKey , before + value + after ) ;
266240 placeholder = value ;
267241 } ) ;
268- return ;
242+ return unsub ;
269243 }
270244
271245 setNodeAttribute ( node , attributeKey , before + arg + after ) ;
246+ return null ;
272247}
273248
274249function setNodeAttribute ( node , attributeKey , value ) {
@@ -284,6 +259,7 @@ function html(templateParts, ...args) {
284259
285260 const instanceId = id ++ ;
286261 const htmlPlaceholderIndices = new Set ( ) ;
262+ const unsubscribes = [ ] ;
287263
288264 const htmlWithPlaceholders = templateParts . reduce ( ( acc , part , i ) => {
289265 if ( i === 0 ) {
@@ -298,9 +274,7 @@ function html(templateParts, ...args) {
298274 }
299275 args [ i ] = "" ;
300276 }
301- const amountCloseTags = ( ( acc + part ) . match ( / > / g) || [ ] ) . length ;
302- const amountOpenTags = ( ( acc + part ) . match ( / < / g) || [ ] ) . length ;
303- const isAttribute = amountCloseTags !== amountOpenTags ;
277+ const isAttribute = isInsideTag ( acc + part ) ;
304278 if ( isAttribute ) {
305279 return acc + part + makePlaceholderId ( i , instanceId ) ;
306280 }
@@ -317,7 +291,8 @@ function html(templateParts, ...args) {
317291 if ( ! placeholderNode ) {
318292 throw new Error ( "Fatal: Could not find placeholder for argument: " + i ) ;
319293 }
320- replacePlaceholderNode ( placeholderNode , arg ) ;
294+ const unsub = replacePlaceholderNode ( placeholderNode , arg ) ;
295+ if ( unsub ) unsubscribes . push ( unsub ) ;
321296 } else {
322297 let [ node , attributeKey ] = findNodeByAttributeValue ( fakeParent . content , makePlaceholderId ( i , instanceId ) ) ;
323298
@@ -331,14 +306,33 @@ function html(templateParts, ...args) {
331306 }
332307
333308 if ( node . tagName === "HOLD-PASS" ) {
334- setTimeout ( ( ) => replaceAttributePlaceholder ( node , attributeKey , arg , makePlaceholderId ( i , instanceId ) ) ) ;
309+ setTimeout ( ( ) => {
310+ const unsub = replaceAttributePlaceholder ( node , attributeKey , arg , makePlaceholderId ( i , instanceId ) ) ;
311+ if ( unsub ) unsubscribes . push ( unsub ) ;
312+ } ) ;
335313 } else {
336- replaceAttributePlaceholder ( node , attributeKey , arg , makePlaceholderId ( i , instanceId ) ) ;
314+ const unsub = replaceAttributePlaceholder ( node , attributeKey , arg , makePlaceholderId ( i , instanceId ) ) ;
315+ if ( unsub ) unsubscribes . push ( unsub ) ;
337316 }
338317 }
339318 } ) ;
340319
341- return fakeParent . content . childNodes . length > 1 ? Array . from ( fakeParent . content . childNodes ) : fakeParent . content . firstChild ;
320+ const result = fakeParent . content . childNodes . length > 1 ? Array . from ( fakeParent . content . childNodes ) : fakeParent . content . firstChild ;
321+
322+ function dispose ( ) {
323+ for ( const unsub of unsubscribes ) {
324+ if ( typeof unsub === "function" ) unsub ( ) ;
325+ }
326+ unsubscribes . length = 0 ;
327+ }
328+
329+ if ( Array . isArray ( result ) ) {
330+ result . dispose = dispose ;
331+ } else if ( result ) {
332+ result . dispose = dispose ;
333+ }
334+
335+ return result ;
342336}
343337
344338customElements . define ( "hold-pass" , class extends HTMLElement {
0 commit comments