@@ -120,6 +120,33 @@ const setNodeText = (node, text) => {
120120return node ;
121121} ;
122122
123+ // class TrackingProxy extends Proxy {
124+ // constructor(target) {
125+ // super(target, {
126+ // get: this.trackAccess.bind(this),
127+ // });
128+ // this.accessedProperties = new Set();
129+ // }
130+ //
131+ // trackAccess(target, property) {
132+ // this.accessedProperties.add(property);
133+ // return target[property];
134+ // }
135+ // }
136+
137+ const createTrackingProxy = ( data ) => {
138+ const accessed = new Set ( ) ;
139+ return [
140+ new Proxy ( data , {
141+ get ( target , property ) {
142+ accessed . add ( property ) ;
143+ return target [ property ] ;
144+ } ,
145+ } ) ,
146+ accessed ,
147+ ] ;
148+ } ;
149+
123150class UIEvent {
124151constructor ( name , data , origin ) {
125152this . name = name ;
@@ -201,7 +228,7 @@ class UITemplateSlot {
201228this . predicatePlaceholder = undefined ;
202229}
203230
204- apply ( nodes , parent ) {
231+ apply ( nodes , parent , raw = false ) {
205232let node = nodes ;
206233for ( const i of this . path ) {
207234if ( node instanceof Array ) {
@@ -210,7 +237,7 @@ class UITemplateSlot {
210237node = node ?node . childNodes [ i ] :node ;
211238}
212239}
213- return node ?new UISlot ( node , this , parent ) :null ;
240+ return node ?( raw ? node : new UISlot ( node , this , parent ) ) :null ;
214241}
215242}
216243
@@ -228,6 +255,7 @@ class UITemplate {
228255this . in = UITemplateSlot . Find ( "in" , nodes ) ;
229256this . out = UITemplateSlot . Find ( "out" , nodes ) ;
230257this . inout = UITemplateSlot . Find ( "inout" , nodes ) ;
258+ this . ref = UITemplateSlot . Find ( "ref" , nodes ) ;
231259this . when = UITemplateSlot . Find ( "when" , nodes , ( slot , expr ) => {
232260slot . predicate = new Function (
233261`return ((self,data,event)=>(${ expr } ))`
@@ -236,6 +264,7 @@ class UITemplate {
236264return slot ;
237265} ) ;
238266// Interaction/Behavior (accessed from UIInstance)
267+ this . initializer = undefined ;
239268this . behavior = undefined ;
240269this . subs = undefined ;
241270}
@@ -258,6 +287,11 @@ class UITemplate {
258287// BEHAVIOUR
259288// ========================================================================
260289
290+ init ( init ) {
291+ this . initializer = init ;
292+ return this ;
293+ }
294+
261295does ( behavior ) {
262296this . behavior = Object . assign ( this . behavior ?? { } , behavior ) ;
263297return this ;
@@ -267,6 +301,7 @@ class UITemplate {
267301// EVENTS
268302// ========================================================================
269303
304+ // FIXME: Should be on
270305sub ( event , handler = undefined ) {
271306if ( typeof event === "string" ) {
272307if ( ! handler ) {
@@ -338,9 +373,18 @@ class UISlot {
338373// the default item is `_`
339374const items = t === type . List || t === type . Dict ?data :{ _ :data } ;
340375let previous = null ;
376+ // NOTE: Mapping values can be:
377+ // - A `UIInstance` (it's an applied slot, ie. it has a UITemplate)
378+ // - A node when no UITemplate, but the given items is a node
379+ // - A text node (no UITemplate)
380+ // Items can be:
381+ // - An AppliedUITemplate, in which case we create a new instance
382+ // - A DOM node, in which case we add the DOM node as is
383+ // - Anything else, which is then converted to text.
341384for ( const k in items ) {
342385const item = items [ k ] ;
343386if ( ! this . mapping . has ( k ) ) {
387+ // Creation: we don't have mapping for the item
344388let r = undefined ;
345389if ( item instanceof AppliedUITemplate ) {
346390r = item . template . new ( this . parent ) ;
@@ -349,6 +393,10 @@ class UISlot {
349393} else if ( isInputNode ( this . node ) ) {
350394setNodeText ( this . node , asText ( item ) ) ;
351395r = this . node ;
396+ } else if ( item instanceof Node ) {
397+ // TODO: Insert after previous
398+ this . node . appendChild ( item ) ;
399+ r = item ;
352400} else {
353401r = document . createTextNode ( asText ( item ) ) ;
354402// TODO: Use mount and sibling
@@ -357,22 +405,43 @@ class UISlot {
357405}
358406this . mapping . set ( k , r ) ;
359407} else {
408+ // Update: we do have a key like that
360409const r = this . mapping . get ( k ) ;
361410if ( r instanceof UIInstance ) {
362411if ( item instanceof AppliedUITemplate ) {
363412if ( item . template === r . template ) {
364413r . set ( item . data , k ) ;
365414} else {
415+ // It's a different template. We need to unmount the
416+ // current instance and replace it with the new one.
366417console . error ( "Not implemented: change in element" ) ;
367418}
368419} else {
369420r . set ( item , k ) ;
370421}
422+ } else if ( isInputNode ( this . node ) ) {
423+ setNodeText ( this . node , asText ( item ) ) ;
424+ } else if ( r ?. nodeType === Node . ELEMENT_NODE ) {
425+ if ( item instanceof AppliedUITemplate ) {
426+ console . error (
427+ "Not implemented: change from non UIInstance to UIInstance"
428+ ) ;
429+ } else if ( item instanceof Node ) {
430+ r . parentNode . replaceChild ( item , r ) ;
431+ this . mapping . set ( k , item ) ;
432+ } else {
433+ const t = document . createTextNode ( asText ( item ) ) ;
434+ r . parentNode . replaceChild ( t , r ) ;
435+ this . mapping . set ( k , t ) ;
436+ }
371437} else {
372438if ( item instanceof AppliedUITemplate ) {
373439console . error (
374440"Not implemented: change from non UIInstance to UIInstance"
375441) ;
442+ } else if ( item instanceof Node ) {
443+ r . parentNode . replaceChild ( item , r ) ;
444+ this . mapping . set ( k , item ) ;
376445} else {
377446setNodeText ( r , asText ( item ) ) ;
378447}
@@ -420,6 +489,12 @@ class UISlot {
420489}
421490}
422491
492+ class BehaviorState {
493+ constructor ( ) {
494+ this . value = undefined ;
495+ this . dependencies = new Set ( ) ;
496+ }
497+ }
423498// ----------------------------------------------------------------------------
424499//
425500// UI INSTANCE
@@ -444,6 +519,10 @@ class UIInstance {
444519this . inout = remap ( template . inout , ( _ ) =>
445520remap ( _ , ( _ ) => _ . apply ( this . nodes , this ) )
446521) ;
522+ this . ref = remap ( template . ref , ( _ ) => {
523+ const r = remap ( _ , ( _ ) => _ . apply ( this . nodes , this , true ) ) ;
524+ return r . length === 1 ?r [ 0 ] :r ;
525+ } ) ;
447526this . on = remap ( template . on , ( _ ) =>
448527remap ( _ , ( _ ) => _ . apply ( this . nodes , this ) )
449528) ;
@@ -459,12 +538,42 @@ class UIInstance {
459538this . behavior = new Map ( ) ;
460539this . predicate = undefined ;
461540this . bind ( ) ;
541+ this . initial = undefined ;
542+ this . _renderer = ( ) => this . render ( ) ;
543+ if ( template . initializer ) {
544+ const state = template . initializer ( ) ;
545+ if ( state ) {
546+ for ( const k in state ) {
547+ const v = state [ k ] ;
548+ if ( v && v ?. isReactive ) {
549+ // FIXME: This does a FULL render, even on a single
550+ // cell change.
551+ v . sub ( this . _renderer ) ;
552+ }
553+ }
554+ this . initial = state ;
555+ }
556+ this . set ( state ) ;
557+ }
462558}
463559
464560// ========================================================================
465561// BEHAVIOR
466562// ========================================================================
467563
564+ dispose ( ) {
565+ if ( this . initial ) {
566+ for ( const k in this . initial ) {
567+ const v = this . initial [ k ] ;
568+ if ( v && v ?. isReactive ) {
569+ // FIXME: This does a FULL render, even on a single
570+ // cell change.
571+ v . unsub ( this . _renderer ) ;
572+ }
573+ }
574+ }
575+ }
576+
468577bind ( ) {
469578// We bind the event handlers
470579for ( const set of [ this . on , this . in , this . inout ] ) {
@@ -517,15 +626,23 @@ class UIInstance {
517626return this ;
518627}
519628
520- update ( data ) {
521- let same = true ;
629+ update ( data , force = false ) {
630+ let same = force ? false : true ;
522631if ( ! this . data ) {
523632same = false ;
524- } else {
633+ } else if ( same ) {
525634for ( const k in data ) {
526- if ( ! eq ( data [ k ] , this . data [ k ] ) ) {
635+ const existing = this . data [ k ] ;
636+ const updated = data [ k ] ;
637+ if ( ! eq ( existing , updated ) ) {
527638same = false ;
528- break ;
639+ // We sub/unsub if there's a reactive cell in the update
640+ if ( existing ?. isReactive ) {
641+ existing . unsub ( this . _renderer ) ;
642+ }
643+ if ( updated ?. isReactive ) {
644+ updated . sub ( this . _renderer ) ;
645+ }
529646}
530647}
531648}
@@ -578,7 +695,12 @@ class UIInstance {
578695if ( typeof node === "string" ) {
579696const n = document . querySelector ( node ) ;
580697if ( ! n ) {
581- console . error ( "Selector is empty" , node ) ;
698+ console . error (
699+ "Selector is empty, cannot mounted component" ,
700+ node ,
701+ { component :this . template }
702+ ) ;
703+ return this ;
582704} else {
583705node = n ;
584706}
@@ -614,7 +736,8 @@ class UIInstance {
614736// --
615737// Renders the given data, using `create`, `update` and `remove`
616738// functions
617- render ( data ) {
739+ // TODO: Should take a "changes" and know which behaviour should be updated
740+ render ( data = this . data ) {
618741const data_type = type ( data ) ;
619742// FIXME: I'm not sure this condition is good.
620743if (
@@ -643,6 +766,10 @@ class UIInstance {
643766v = this . behavior . get ( k ) ;
644767} else {
645768const b = this . template . behavior [ k ] ;
769+ // const [tracked_data, accessed] =
770+ // createTrackingProxy(data);
771+ // v = b(this, tracked_data, null);
772+ // console.log("TRACKED_DATA", accessed);
646773v = b ( this , data , null ) ;
647774this . behavior . set ( k , v ) ;
648775}
@@ -692,15 +819,18 @@ export const ui = (selection, scope = document) => {
692819}
693820}
694821}
822+ // TODO: Should retrieve id and assign a name.
695823const tmpl = new UITemplate ( nodes ) ;
696824const component = ( ...args ) => tmpl . apply ( ...args ) ;
697825Object . assign ( component , {
698826isTemplate :true ,
699827template :tmpl ,
700828new :( ...args ) => tmpl . new ( ...args ) ,
829+ init :( ...args ) => tmpl . init ( ...args ) ,
701830map :( ...args ) => tmpl . map ( ...args ) ,
702831apply :( ...args ) => tmpl . apply ( ...args ) ,
703832does :( ...args ) => ( tmpl . does ( ...args ) , component ) ,
833+ on :( ...args ) => ( tmpl . sub ( ...args ) , component ) ,
704834sub :( ...args ) => ( tmpl . sub ( ...args ) , component ) ,
705835} ) ;
706836return component ;