2023-09-07 17:35:23 +02:00
export const mark = Symbol . for ( "Signal" ) ;
2023-09-05 09:25:47 +02:00
export function isSignal ( candidate ) {
try { return Reflect . has ( candidate , mark ) ; }
catch ( e ) { return false ; }
}
2023-09-08 10:52:45 +02:00
/** @type {WeakMap<function,Set<ddeSignal<any, any>>>} */
const deps = new WeakMap ( ) ;
2023-09-05 09:25:47 +02:00
export function S ( value , actions ) {
if ( typeof value !== "function" )
return create ( value , actions ) ;
if ( isSignal ( value ) ) return value ;
2023-09-08 10:52:45 +02:00
const out = create ( "" ) ;
const context = ( ) => out ( value ( ) ) ;
deps . set ( context , new Set ( [ out ] ) ) ;
watch ( context ) ;
2023-09-05 09:25:47 +02:00
return out ;
}
S . action = function ( signal , name , ... a ) {
const s = signal [ mark ] , { actions } = s ;
if ( ! actions || ! Reflect . has ( actions , name ) )
throw new Error ( ` ' ${ signal } ' has no action with name ' ${ name } '! ` ) ;
actions [ name ] . apply ( s , a ) ;
if ( s . skip ) return Reflect . deleteProperty ( s , "skip" ) ;
s . listeners . forEach ( l => l ( s . value ) ) ;
} ;
2023-09-07 17:35:23 +02:00
S . on = function on ( signals , listener , options = { } ) {
const { signal : as } = options ;
if ( as && as . aborted ) return ;
2023-09-05 09:25:47 +02:00
if ( Array . isArray ( signals ) ) return signals . forEach ( s => on ( s , listener , options ) ) ;
addSignalListener ( signals , listener ) ;
2023-09-07 17:35:23 +02:00
if ( as ) as . addEventListener ( "abort" , ( ) => removeSignalListener ( signals , listener ) ) ;
2023-09-07 18:21:51 +02:00
//TODO cleanup when signal removed
2023-09-07 13:52:09 +02:00
} ;
2023-09-07 17:35:23 +02:00
S . symbols = {
signal : mark ,
onclear : Symbol . for ( "Signal.onclear" )
} ;
2023-09-05 09:25:47 +02:00
S . clear = function ( ... signals ) {
for ( const signal of signals ) {
2023-09-08 20:16:42 +02:00
Reflect . deleteProperty ( signal , "toJSON" ) ;
2023-09-07 17:35:23 +02:00
const s = signal [ mark ] ;
const { onclear } = S . symbols ;
if ( s . actions && s . actions [ onclear ] )
s . actions [ onclear ] . call ( s ) ;
2023-09-08 10:52:45 +02:00
clearListDeps ( signal , s ) ;
2023-09-05 09:25:47 +02:00
Reflect . deleteProperty ( signal , mark ) ;
}
2023-09-08 10:52:45 +02:00
function clearListDeps ( signal , s ) {
s . listeners . forEach ( l => {
s . listeners . delete ( l ) ;
if ( ! deps . has ( l ) ) return ;
const ls = deps . get ( l ) ;
ls . delete ( signal ) ;
if ( ls . size > 1 ) return ;
S . clear ( ... ls ) ;
deps . delete ( l ) ;
} ) ;
}
2023-09-05 09:25:47 +02:00
} ;
2023-09-08 20:16:42 +02:00
S . el = function ( signal , map ) {
const mark _start = document . createComment ( "<#reactive>" ) ;
const mark _end = document . createComment ( "</#reactive>" ) ;
const out = document . createDocumentFragment ( ) ;
out . append ( mark _start , mark _end ) ;
const reRenderReactiveElement = v => {
if ( ! mark _start . parentNode || ! mark _end . parentNode )
return removeSignalListener ( signal , reRenderReactiveElement ) ;
let els = map ( v ) ;
if ( ! Array . isArray ( els ) )
els = [ els ] ;
let el _r = mark _start ;
while ( ( el _r = mark _start . nextSibling ) !== mark _end )
el _r . remove ( ) ;
mark _start . after ( ... els ) ;
} ;
addSignalListener ( signal , reRenderReactiveElement ) ;
reRenderReactiveElement ( signal ( ) ) ;
return out ;
} ;
2023-09-05 09:25:47 +02:00
import { typeOf } from './helpers.js' ;
export const signals _config = {
2023-09-21 12:35:27 +02:00
isSignal ,
2023-09-11 18:30:06 +02:00
processReactiveAttribute ( _ , key , attrs , assignNth ) {
//TODO DOC: once the signal is used as attribute, there is no reason to use assign again (if for some reason needed, use imperative listeners clear with `S.clear`)
if ( ! isSignal ( attrs ) ) return attrs ;
addSignalListener ( attrs , attr => assignNth ( [ key , attr ] ) ) ;
return attrs ( ) ;
2023-09-05 09:25:47 +02:00
}
} ;
function create ( value , actions ) {
const signal = ( ... value ) =>
value . length ? write ( signal , value [ 0 ] ) : read ( signal ) ;
return toSignal ( signal , value , actions ) ;
}
const protoSigal = Object . assign ( Object . create ( null ) , {
stopPropagation ( ) {
this . skip = true ;
}
} ) ;
function toSignal ( signal , value , actions ) {
if ( typeOf ( actions ) !== "[object Object]" )
actions = { } ;
signal [ mark ] = {
value , actions ,
2023-09-08 20:16:42 +02:00
listeners : new Set ( )
2023-09-05 09:25:47 +02:00
} ;
2023-09-08 20:16:42 +02:00
signal . toJSON = ( ) => signal ( ) ;
2023-09-05 09:25:47 +02:00
Object . setPrototypeOf ( signal [ mark ] , protoSigal ) ;
return signal ;
}
2023-09-08 10:52:45 +02:00
/** @type {function[]} */
2023-09-05 09:25:47 +02:00
const stack _watch = [ ] ;
2023-09-08 10:52:45 +02:00
function watch ( context ) {
2023-09-05 09:25:47 +02:00
const contextReWatch = function ( ) {
stack _watch . push ( contextReWatch ) ;
context ( ) ;
stack _watch . pop ( ) ;
} ;
2023-09-08 10:52:45 +02:00
//reassign deps as final context is contextReWatch
if ( deps . has ( context ) ) { deps . set ( contextReWatch , deps . get ( context ) ) ; deps . delete ( context ) ; }
contextReWatch ( ) ;
2023-09-07 13:52:09 +02:00
}
2023-09-05 09:25:47 +02:00
function currentContext ( ) {
return stack _watch [ stack _watch . length - 1 ] ;
}
function read ( signal ) {
if ( ! signal [ mark ] ) return ;
const { value , listeners } = signal [ mark ] ;
const context = currentContext ( ) ;
if ( context ) listeners . add ( context ) ;
2023-09-08 10:52:45 +02:00
if ( deps . has ( context ) ) deps . get ( context ) . add ( signal ) ;
2023-09-05 09:25:47 +02:00
return value ;
}
function write ( signal , value ) {
if ( ! signal [ mark ] ) return ;
const s = signal [ mark ] ;
if ( s . value === value ) return ;
s . value = value ;
2023-09-08 10:52:45 +02:00
s . listeners . forEach ( l => l ( value ) ) ;
2023-09-05 09:25:47 +02:00
return value ;
}
function valueOfSignal ( signal ) {
return signal [ mark ] . value ;
}
function addSignalListener ( signal , listener ) {
2023-09-09 21:15:43 +02:00
if ( ! signal [ mark ] ) return ;
2023-09-05 09:25:47 +02:00
return signal [ mark ] . listeners . add ( listener ) ;
}
function removeSignalListener ( signal , listener ) {
2023-09-09 21:15:43 +02:00
if ( ! signal [ mark ] ) return ;
2023-09-05 09:25:47 +02:00
return signal [ mark ] . listeners . delete ( listener ) ;
}