Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2b0f795

Browse files
committed
[Update] ui: init, cell support, HTML nodes as value.
1 parentd2f7119 commit2b0f795

File tree

2 files changed

+207
-9
lines changed

2 files changed

+207
-9
lines changed

‎examples/colorpicker.html‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<metacharset="utf-8"/>
6+
<title>Select UI ― Reactive Cells</title>
7+
<metaname="viewport"content="width=device-width, initial-scale=1.0">
8+
<scripttype="importmap">
9+
{"imports":{"@./":"../src/js/"}}
10+
</script>
11+
</head>
12+
13+
<body>
14+
<divid="ui">
15+
</div>
16+
17+
<templateid="ColorComponent">
18+
<div>
19+
<labelout="label">Component</label>
20+
<inputinout="value"type="range"min="0"max="255"/>
21+
</div>
22+
</template>
23+
24+
<templateid="ColorPicker">
25+
<div>
26+
<divout="color"style="width:32px;height:32px;border:1px solid black"ref="swatch"></div>
27+
<div>
28+
<divout="red"></div>
29+
<divout="green"></div>
30+
<divout="blue"></div>
31+
</div>
32+
</div>
33+
</template>
34+
35+
36+
37+
<scripttype="module">
38+
importuifrom"@./select.ui.js"
39+
importcell,{derived}from"@./select.cells.js"
40+
41+
// This shows how we're using cells to manage shared state and updates.
42+
constColorComponent=ui("#ColorComponent").does({
43+
label:(self,{label})=>label,
44+
value:(self,{value},event)=>Math.min(255,Math.max(0,event ?value.set(event.target.value) :(value.value??0))),
45+
});
46+
constColorPicker=ui("#ColorPicker").init(()=>{
47+
constred=cell(100);
48+
constgreen=cell(130);
49+
constblue=cell(10);
50+
constcolor=derived([red,green,blue],(red,green,blue)=>{
51+
return`rgb(${red},${green},${blue})`;
52+
})
53+
return{red, green, blue, color}
54+
}).does({
55+
// TODO: If we were able to keep track of what is used, we could only
56+
// re-trigger the behaviors that have changed.
57+
red:(self,data)=>ColorComponent({value:data.red,label:"Red"}),
58+
green:(self,{green})=>ColorComponent({value:green,label:"Green"}),
59+
blue:(self,{blue})=>ColorComponent({value:blue,label:"Blue"}),
60+
color:(self,{color})=>{
61+
self.ref.swatch.style.backgroundColor=color.value;
62+
}
63+
});
64+
ColorPicker.new().mount("#ui");
65+
</script>
66+
</body>
67+
68+
</html>

‎src/js/select.ui.js‎

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,33 @@ const setNodeText = (node, text) => {
120120
returnnode;
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+
constcreateTrackingProxy=(data)=>{
138+
constaccessed=newSet();
139+
return[
140+
newProxy(data,{
141+
get(target,property){
142+
accessed.add(property);
143+
returntarget[property];
144+
},
145+
}),
146+
accessed,
147+
];
148+
};
149+
123150
classUIEvent{
124151
constructor(name,data,origin){
125152
this.name=name;
@@ -201,7 +228,7 @@ class UITemplateSlot {
201228
this.predicatePlaceholder=undefined;
202229
}
203230

204-
apply(nodes,parent){
231+
apply(nodes,parent,raw=false){
205232
letnode=nodes;
206233
for(constiofthis.path){
207234
if(nodeinstanceofArray){
@@ -210,7 +237,7 @@ class UITemplateSlot {
210237
node=node ?node.childNodes[i] :node;
211238
}
212239
}
213-
returnnode ?newUISlot(node,this,parent) :null;
240+
returnnode ?(raw ?node :newUISlot(node,this,parent)) :null;
214241
}
215242
}
216243

@@ -228,6 +255,7 @@ class UITemplate {
228255
this.in=UITemplateSlot.Find("in",nodes);
229256
this.out=UITemplateSlot.Find("out",nodes);
230257
this.inout=UITemplateSlot.Find("inout",nodes);
258+
this.ref=UITemplateSlot.Find("ref",nodes);
231259
this.when=UITemplateSlot.Find("when",nodes,(slot,expr)=>{
232260
slot.predicate=newFunction(
233261
`return ((self,data,event)=>(${expr}))`
@@ -236,6 +264,7 @@ class UITemplate {
236264
returnslot;
237265
});
238266
// Interaction/Behavior (accessed from UIInstance)
267+
this.initializer=undefined;
239268
this.behavior=undefined;
240269
this.subs=undefined;
241270
}
@@ -258,6 +287,11 @@ class UITemplate {
258287
// BEHAVIOUR
259288
// ========================================================================
260289

290+
init(init){
291+
this.initializer=init;
292+
returnthis;
293+
}
294+
261295
does(behavior){
262296
this.behavior=Object.assign(this.behavior??{},behavior);
263297
returnthis;
@@ -267,6 +301,7 @@ class UITemplate {
267301
// EVENTS
268302
// ========================================================================
269303

304+
// FIXME: Should be on
270305
sub(event,handler=undefined){
271306
if(typeofevent==="string"){
272307
if(!handler){
@@ -338,9 +373,18 @@ class UISlot {
338373
// the default item is `_`
339374
constitems=t===type.List||t===type.Dict ?data :{_:data};
340375
letprevious=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.
341384
for(constkinitems){
342385
constitem=items[k];
343386
if(!this.mapping.has(k)){
387+
// Creation: we don't have mapping for the item
344388
letr=undefined;
345389
if(iteminstanceofAppliedUITemplate){
346390
r=item.template.new(this.parent);
@@ -349,6 +393,10 @@ class UISlot {
349393
}elseif(isInputNode(this.node)){
350394
setNodeText(this.node,asText(item));
351395
r=this.node;
396+
}elseif(iteminstanceofNode){
397+
// TODO: Insert after previous
398+
this.node.appendChild(item);
399+
r=item;
352400
}else{
353401
r=document.createTextNode(asText(item));
354402
// TODO: Use mount and sibling
@@ -357,22 +405,43 @@ class UISlot {
357405
}
358406
this.mapping.set(k,r);
359407
}else{
408+
// Update: we do have a key like that
360409
constr=this.mapping.get(k);
361410
if(rinstanceofUIInstance){
362411
if(iteminstanceofAppliedUITemplate){
363412
if(item.template===r.template){
364413
r.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.
366417
console.error("Not implemented: change in element");
367418
}
368419
}else{
369420
r.set(item,k);
370421
}
422+
}elseif(isInputNode(this.node)){
423+
setNodeText(this.node,asText(item));
424+
}elseif(r?.nodeType===Node.ELEMENT_NODE){
425+
if(iteminstanceofAppliedUITemplate){
426+
console.error(
427+
"Not implemented: change from non UIInstance to UIInstance"
428+
);
429+
}elseif(iteminstanceofNode){
430+
r.parentNode.replaceChild(item,r);
431+
this.mapping.set(k,item);
432+
}else{
433+
constt=document.createTextNode(asText(item));
434+
r.parentNode.replaceChild(t,r);
435+
this.mapping.set(k,t);
436+
}
371437
}else{
372438
if(iteminstanceofAppliedUITemplate){
373439
console.error(
374440
"Not implemented: change from non UIInstance to UIInstance"
375441
);
442+
}elseif(iteminstanceofNode){
443+
r.parentNode.replaceChild(item,r);
444+
this.mapping.set(k,item);
376445
}else{
377446
setNodeText(r,asText(item));
378447
}
@@ -420,6 +489,12 @@ class UISlot {
420489
}
421490
}
422491

492+
classBehaviorState{
493+
constructor(){
494+
this.value=undefined;
495+
this.dependencies=newSet();
496+
}
497+
}
423498
// ----------------------------------------------------------------------------
424499
//
425500
// UI INSTANCE
@@ -444,6 +519,10 @@ class UIInstance {
444519
this.inout=remap(template.inout,(_)=>
445520
remap(_,(_)=>_.apply(this.nodes,this))
446521
);
522+
this.ref=remap(template.ref,(_)=>{
523+
constr=remap(_,(_)=>_.apply(this.nodes,this,true));
524+
returnr.length===1 ?r[0] :r;
525+
});
447526
this.on=remap(template.on,(_)=>
448527
remap(_,(_)=>_.apply(this.nodes,this))
449528
);
@@ -459,12 +538,42 @@ class UIInstance {
459538
this.behavior=newMap();
460539
this.predicate=undefined;
461540
this.bind();
541+
this.initial=undefined;
542+
this._renderer=()=>this.render();
543+
if(template.initializer){
544+
conststate=template.initializer();
545+
if(state){
546+
for(constkinstate){
547+
constv=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(constkinthis.initial){
567+
constv=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+
468577
bind(){
469578
// We bind the event handlers
470579
for(constsetof[this.on,this.in,this.inout]){
@@ -517,15 +626,23 @@ class UIInstance {
517626
returnthis;
518627
}
519628

520-
update(data){
521-
letsame=true;
629+
update(data,force=false){
630+
letsame=force ?false :true;
522631
if(!this.data){
523632
same=false;
524-
}else{
633+
}elseif(same){
525634
for(constkindata){
526-
if(!eq(data[k],this.data[k])){
635+
constexisting=this.data[k];
636+
constupdated=data[k];
637+
if(!eq(existing,updated)){
527638
same=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 {
578695
if(typeofnode==="string"){
579696
constn=document.querySelector(node);
580697
if(!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+
returnthis;
582704
}else{
583705
node=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){
618741
constdata_type=type(data);
619742
// FIXME: I'm not sure this condition is good.
620743
if(
@@ -643,6 +766,10 @@ class UIInstance {
643766
v=this.behavior.get(k);
644767
}else{
645768
constb=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);
646773
v=b(this,data,null);
647774
this.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.
695823
consttmpl=newUITemplate(nodes);
696824
constcomponent=(...args)=>tmpl.apply(...args);
697825
Object.assign(component,{
698826
isTemplate:true,
699827
template:tmpl,
700828
new:(...args)=>tmpl.new(...args),
829+
init:(...args)=>tmpl.init(...args),
701830
map:(...args)=>tmpl.map(...args),
702831
apply:(...args)=>tmpl.apply(...args),
703832
does:(...args)=>(tmpl.does(...args),component),
833+
on:(...args)=>(tmpl.sub(...args),component),
704834
sub:(...args)=>(tmpl.sub(...args),component),
705835
});
706836
returncomponent;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp