@ -533,856 +533,6 @@ export function class_toggle_effect(dom, class_name, value) {
} ) ;
}
/ * *
* Selects the correct option ( s ) ( depending on whether this is a multiple select )
* @ template V
* @ param { HTMLSelectElement } select
* @ param { V } value
* @ param { boolean } [ mounting ]
* /
export function select _option ( select , value , mounting ) {
if ( select . multiple ) {
return select _options ( select , value ) ;
}
for ( const option of select . options ) {
const option _value = get _option _value ( option ) ;
if ( option _value === value ) {
option . selected = true ;
return ;
}
}
if ( ! mounting || value !== undefined ) {
select . selectedIndex = - 1 ; // no option should be selected
}
}
/ * *
* @ template V
* @ param { HTMLSelectElement } select
* @ param { V } value
* /
function select _options ( select , value ) {
for ( const option of select . options ) {
// @ts-ignore
option . selected = ~ value . indexOf ( get _option _value ( option ) ) ;
}
}
/** @param {HTMLOptionElement} option */
function get _option _value ( option ) {
// __value only exists if the <option> has a value attribute
if ( '__value' in option ) {
return option . _ _value ;
} else {
return option . value ;
}
}
/ * *
* @ param { ( online : boolean ) => void } update
* @ returns { void }
* /
export function bind _online ( update ) {
const status _changed = ( ) => {
update ( navigator . onLine ) ;
} ;
listen _to _events ( window , [ 'online' , 'offline' ] , status _changed ) ;
}
/** @param {TimeRanges} ranges */
function time _ranges _to _array ( ranges ) {
const array = [ ] ;
for ( let i = 0 ; i < ranges . length ; i += 1 ) {
array . push ( { start : ranges . start ( i ) , end : ranges . end ( i ) } ) ;
}
return array ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( ) => number | undefined } get _value
* @ param { ( value : number ) => void } update
* @ returns { void }
* /
export function bind _current _time ( media , get _value , update ) {
/** @type {number} */
let raf _id ;
let updating = false ;
// Ideally, listening to timeupdate would be enough, but it fires too infrequently for the currentTime
// binding, which is why we use a raf loop, too. We additionally still listen to timeupdate because
// the user could be scrubbing through the video using the native controls when the media is paused.
const callback = ( ) => {
cancelAnimationFrame ( raf _id ) ;
if ( ! media . paused ) {
raf _id = requestAnimationFrame ( callback ) ;
}
updating = true ;
update ( media . currentTime ) ;
} ;
raf _id = requestAnimationFrame ( callback ) ;
media . addEventListener ( 'timeupdate' , callback ) ;
render _effect ( ( ) => {
const value = get _value ( ) ;
// through isNaN we also allow number strings, which is more robust
if ( ! updating && ! isNaN ( /** @type {any} */ ( value ) ) ) {
media . currentTime = /** @type {number} */ ( value ) ;
}
updating = false ;
} ) ;
render _effect ( ( ) => ( ) => cancelAnimationFrame ( raf _id ) ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( array : Array < { start : number ; end : number } > ) => void } update
* /
export function bind _buffered ( media , update ) {
const callback = ( ) => {
update ( time _ranges _to _array ( media . buffered ) ) ;
} ;
listen _to _events ( media , [ 'loadedmetadata' , 'progress' ] , callback ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( array : Array < { start : number ; end : number } > ) => void } update
* /
export function bind _seekable ( media , update ) {
const callback = ( ) => {
update ( time _ranges _to _array ( media . seekable ) ) ;
} ;
listen _to _events ( media , [ 'loadedmetadata' ] , callback ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( array : Array < { start : number ; end : number } > ) => void } update
* /
export function bind _played ( media , update ) {
const callback = ( ) => {
update ( time _ranges _to _array ( media . played ) ) ;
} ;
listen _to _events ( media , [ 'timeupdate' ] , callback ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( seeking : boolean ) => void } update
* /
export function bind _seeking ( media , update ) {
const callback = ( ) => {
update ( media . seeking ) ;
} ;
listen _to _events ( media , [ 'seeking' , 'seeked' ] , callback ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( seeking : boolean ) => void } update
* /
export function bind _ended ( media , update ) {
const callback = ( ) => {
update ( media . ended ) ;
} ;
listen _to _events ( media , [ 'timeupdate' , 'ended' ] , callback ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( ready _state : number ) => void } update
* /
export function bind _ready _state ( media , update ) {
const callback = ( ) => {
update ( media . readyState ) ;
} ;
listen _to _events (
media ,
[ 'loadedmetadata' , 'loadeddata' , 'canplay' , 'canplaythrough' , 'playing' , 'waiting' , 'emptied' ] ,
callback
) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( ) => number | undefined } get _value
* @ param { ( playback _rate : number ) => void } update
* /
export function bind _playback _rate ( media , get _value , update ) {
let updating = false ;
const callback = ( ) => {
if ( ! updating ) {
update ( media . playbackRate ) ;
}
updating = false ;
} ;
// Needs to happen after the element is inserted into the dom, else playback will be set back to 1 by the browser.
// For hydration we could do it immediately but the additional code is not worth the lost microtask.
/** @type {import('./types.js').Effect | undefined} */
let render ;
let destroyed = false ;
const effect = managed _effect ( ( ) => {
destroy _effect ( effect ) ;
if ( destroyed ) return ;
if ( get _value ( ) == null ) {
callback ( ) ;
}
listen _to _events ( media , [ 'ratechange' ] , callback , false ) ;
render = render _effect ( ( ) => {
const value = get _value ( ) ;
// through isNaN we also allow number strings, which is more robust
if ( ! isNaN ( /** @type {any} */ ( value ) ) && value !== media . playbackRate ) {
updating = true ;
media . playbackRate = /** @type {number} */ ( value ) ;
}
} ) ;
} ) ;
render _effect ( ( ) => ( ) => {
destroyed = true ;
if ( render ) {
destroy _effect ( render ) ;
}
} ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( ) => boolean | undefined } get _value
* @ param { ( paused : boolean ) => void } update
* /
export function bind _paused ( media , get _value , update ) {
let mounted = hydrating ;
let paused = get _value ( ) ;
const callback = ( ) => {
if ( paused !== media . paused ) {
paused = media . paused ;
update ( ( paused = media . paused ) ) ;
}
} ;
if ( paused == null ) {
callback ( ) ;
}
// Defer listening if not mounted yet so that the first canplay event doesn't cause a potentially wrong update
if ( mounted ) {
// If someone switches the src while media is playing, the player will pause.
// Listen to the canplay event to get notified of this situation.
listen _to _events ( media , [ 'play' , 'pause' , 'canplay' ] , callback , false ) ;
}
render _effect ( ( ) => {
paused = ! ! get _value ( ) ;
if ( paused !== media . paused ) {
const toggle = ( ) => {
mounted = true ;
if ( paused ) {
media . pause ( ) ;
} else {
media . play ( ) . catch ( ( ) => {
update ( ( paused = true ) ) ;
} ) ;
}
} ;
if ( mounted ) {
toggle ( ) ;
} else {
// If this is the first invocation in dom mode, the media element isn't mounted yet,
// and therefore its resource isn't loaded yet. We need to wait for the canplay event
// in this case or else we'll get a "The play() request was interrupted by a new load request" error.
media . addEventListener (
'canplay' ,
( ) => {
listen _to _events ( media , [ 'play' , 'pause' , 'canplay' ] , callback , false ) ;
toggle ( ) ;
} ,
{ once : true }
) ;
}
}
} ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( ) => number | undefined } get _value
* @ param { ( volume : number ) => void } update
* /
export function bind _volume ( media , get _value , update ) {
let updating = false ;
const callback = ( ) => {
updating = true ;
update ( media . volume ) ;
} ;
if ( get _value ( ) == null ) {
callback ( ) ;
}
listen _to _events ( media , [ 'volumechange' ] , callback , false ) ;
render _effect ( ( ) => {
const value = get _value ( ) ;
// through isNaN we also allow number strings, which is more robust
if ( ! updating && ! isNaN ( /** @type {any} */ ( value ) ) ) {
media . volume = /** @type {number} */ ( value ) ;
}
updating = false ;
} ) ;
}
/ * *
* @ param { HTMLVideoElement | HTMLAudioElement } media
* @ param { ( ) => boolean | undefined } get _value
* @ param { ( muted : boolean ) => void } update
* /
export function bind _muted ( media , get _value , update ) {
let updating = false ;
const callback = ( ) => {
updating = true ;
update ( media . muted ) ;
} ;
if ( get _value ( ) == null ) {
callback ( ) ;
}
listen _to _events ( media , [ 'volumechange' ] , callback , false ) ;
render _effect ( ( ) => {
const value = get _value ( ) ;
if ( ! updating ) {
media . muted = ! ! value ;
}
updating = false ;
} ) ;
}
/ * *
* Fires the handler once immediately ( unless corresponding arg is set to ` false ` ) ,
* then listens to the given events until the render effect context is destroyed
* @ param { Element | Window } dom
* @ param { Array < string > } events
* @ param { ( ) => void } handler
* @ param { any } call _handler _immediately
* /
function listen _to _events ( dom , events , handler , call _handler _immediately = true ) {
if ( call _handler _immediately ) {
handler ( ) ;
}
for ( const name of events ) {
dom . addEventListener ( name , handler ) ;
}
render _effect ( ( ) => {
return ( ) => {
for ( const name of events ) {
dom . removeEventListener ( name , handler ) ;
}
} ;
} ) ;
}
/ * *
* Resize observer singleton .
* One listener per element only !
* https : //groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ
* /
class ResizeObserverSingleton {
/** */
# listeners = new WeakMap ( ) ;
/** @type {ResizeObserver | undefined} */
# observer ;
/** @type {ResizeObserverOptions} */
# options ;
/** @static */
static entries = new WeakMap ( ) ;
/** @param {ResizeObserverOptions} options */
constructor ( options ) {
this . # options = options ;
}
/ * *
* @ param { Element } element
* @ param { ( entry : ResizeObserverEntry ) => any } listener
* /
observe ( element , listener ) {
const listeners = this . # listeners . get ( element ) || new Set ( ) ;
listeners . add ( listener ) ;
this . # listeners . set ( element , listeners ) ;
this . # getObserver ( ) . observe ( element , this . # options ) ;
return ( ) => {
const listeners = this . # listeners . get ( element ) ;
listeners . delete ( listener ) ;
if ( listeners . size === 0 ) {
this . # listeners . delete ( element ) ;
/** @type {ResizeObserver} */ ( this . # observer ) . unobserve ( element ) ;
}
} ;
}
# getObserver ( ) {
return (
this . # observer ? ?
( this . # observer = new ResizeObserver (
/** @param {any} entries */ ( entries ) => {
for ( const entry of entries ) {
ResizeObserverSingleton . entries . set ( entry . target , entry ) ;
for ( const listener of this . # listeners . get ( entry . target ) || [ ] ) {
listener ( entry ) ;
}
}
}
) )
) ;
}
}
const resize _observer _content _box = /* @__PURE__ */ new ResizeObserverSingleton ( {
box : 'content-box'
} ) ;
const resize _observer _border _box = /* @__PURE__ */ new ResizeObserverSingleton ( {
box : 'border-box'
} ) ;
const resize _observer _device _pixel _content _box = /* @__PURE__ */ new ResizeObserverSingleton ( {
box : 'device-pixel-content-box'
} ) ;
/ * *
* @ param { Element } dom
* @ param { 'contentRect' | 'contentBoxSize' | 'borderBoxSize' | 'devicePixelContentBoxSize' } type
* @ param { ( entry : keyof ResizeObserverEntry ) => void } update
* /
export function bind _resize _observer ( dom , type , update ) {
const observer =
type === 'contentRect' || type === 'contentBoxSize'
? resize _observer _content _box
: type === 'borderBoxSize'
? resize _observer _border _box
: resize _observer _device _pixel _content _box ;
const unsub = observer . observe ( dom , /** @param {any} entry */ ( entry ) => update ( entry [ type ] ) ) ;
render _effect ( ( ) => unsub ) ;
}
/ * *
* @ param { HTMLElement } dom
* @ param { 'clientWidth' | 'clientHeight' | 'offsetWidth' | 'offsetHeight' } type
* @ param { ( size : number ) => void } update
* /
export function bind _element _size ( dom , type , update ) {
const unsub = resize _observer _border _box . observe ( dom , ( ) => update ( dom [ type ] ) ) ;
effect ( ( ) => {
untrack ( ( ) => update ( dom [ type ] ) ) ;
return unsub ;
} ) ;
}
/ * *
* @ param { 'innerWidth' | 'innerHeight' | 'outerWidth' | 'outerHeight' } type
* @ param { ( size : number ) => void } update
* /
export function bind _window _size ( type , update ) {
const callback = ( ) => update ( window [ type ] ) ;
listen _to _events ( window , [ 'resize' ] , callback ) ;
}
/ * *
* Finds the containing ` <select> ` element and potentially updates its ` selected ` state .
* @ param { HTMLOptionElement } dom
* @ returns { void }
* /
export function selected ( dom ) {
// Inside an effect because the element might not be connected
// to the parent <select> yet when this is called
effect ( ( ) => {
let select = dom . parentNode ;
while ( select != null ) {
if ( select . nodeName === 'SELECT' ) {
break ;
}
select = select . parentNode ;
}
// @ts-ignore
if ( select != null && dom . _ _value === select . _ _value ) {
// never set to false, since this causes browser to select default option
dom . selected = true ;
}
} ) ;
}
/ * *
* @ param { HTMLInputElement } dom
* @ param { ( ) => unknown } get _value
* @ param { ( value : unknown ) => void } update
* @ returns { void }
* /
export function bind _value ( dom , get _value , update ) {
dom . addEventListener ( 'input' , ( ) => {
if ( DEV && dom . type === 'checkbox' ) {
throw new Error (
'Using bind:value together with a checkbox input is not allowed. Use bind:checked instead'
) ;
}
/** @type {any} */
let value = dom . value ;
if ( is _numberlike _input ( dom ) ) {
value = to _number ( value ) ;
}
update ( value ) ;
} ) ;
render _effect ( ( ) => {
if ( DEV && dom . type === 'checkbox' ) {
throw new Error (
'Using bind:value together with a checkbox input is not allowed. Use bind:checked instead'
) ;
}
const value = get _value ( ) ;
// @ts-ignore
dom . _ _value = value ;
if ( is _numberlike _input ( dom ) && value === to _number ( dom . value ) ) {
// handles 0 vs 00 case (see https://github.com/sveltejs/svelte/issues/9959)
return ;
}
if ( dom . type === 'date' && ! value && ! dom . value ) {
// Handles the case where a temporarily invalid date is set (while typing, for example with a leading 0 for the day)
// and prevents this state from clearing the other parts of the date input (see https://github.com/sveltejs/svelte/issues/7897)
return ;
}
dom . value = stringify ( value ) ;
} ) ;
}
/ * *
* @ param { HTMLInputElement } dom
* /
function is _numberlike _input ( dom ) {
const type = dom . type ;
return type === 'number' || type === 'range' ;
}
/ * *
* @ param { string } value
* /
function to _number ( value ) {
return value === '' ? null : + value ;
}
/ * *
* @ param { HTMLSelectElement } dom
* @ param { ( ) => unknown } get _value
* @ param { ( value : unknown ) => void } update
* @ returns { void }
* /
export function bind _select _value ( dom , get _value , update ) {
let mounting = true ;
dom . addEventListener ( 'change' , ( ) => {
/** @type {unknown} */
let value ;
if ( dom . multiple ) {
value = [ ] . map . call ( dom . querySelectorAll ( ':checked' ) , get _option _value ) ;
} else {
/** @type {HTMLOptionElement | null} */
const selected _option = dom . querySelector ( ':checked' ) ;
value = selected _option && get _option _value ( selected _option ) ;
}
update ( value ) ;
} ) ;
// Needs to be an effect, not a render_effect, so that in case of each loops the logic runs after the each block has updated
effect ( ( ) => {
let value = get _value ( ) ;
select _option ( dom , value , mounting ) ;
if ( mounting && value === undefined ) {
/** @type {HTMLOptionElement | null} */
let selected _option = dom . querySelector ( ':checked' ) ;
if ( selected _option !== null ) {
value = get _option _value ( selected _option ) ;
update ( value ) ;
}
}
// @ts-ignore
dom . _ _value = value ;
mounting = false ;
} ) ;
}
/ * *
* @ param { 'innerHTML' | 'textContent' | 'innerText' } property
* @ param { HTMLElement } dom
* @ param { ( ) => unknown } get _value
* @ param { ( value : unknown ) => void } update
* @ returns { void }
* /
export function bind _content _editable ( property , dom , get _value , update ) {
dom . addEventListener ( 'input' , ( ) => {
// @ts-ignore
const value = dom [ property ] ;
update ( value ) ;
} ) ;
render _effect ( ( ) => {
const value = get _value ( ) ;
if ( dom [ property ] !== value ) {
if ( value === null ) {
// @ts-ignore
const non _null _value = dom [ property ] ;
update ( non _null _value ) ;
} else {
// @ts-ignore
dom [ property ] = value + '' ;
}
}
} ) ;
}
/ * *
* @ template V
* @ param { Array < HTMLInputElement > } group
* @ param { V } _ _value
* @ param { boolean } checked
* @ returns { V [ ] }
* /
function get _binding _group _value ( group , _ _value , checked ) {
const value = new Set ( ) ;
for ( let i = 0 ; i < group . length ; i += 1 ) {
if ( group [ i ] . checked ) {
// @ts-ignore
value . add ( group [ i ] . _ _value ) ;
}
}
if ( ! checked ) {
value . delete ( _ _value ) ;
}
return Array . from ( value ) ;
}
/ * *
* @ param { Array < HTMLInputElement > } group
* @ param { null | [ number ] } group _index
* @ param { HTMLInputElement } dom
* @ param { ( ) => unknown } get _value
* @ param { ( value : unknown ) => void } update
* @ returns { void }
* /
export function bind _group ( group , group _index , dom , get _value , update ) {
const is _checkbox = dom . getAttribute ( 'type' ) === 'checkbox' ;
let binding _group = group ;
if ( group _index !== null ) {
for ( const index of group _index ) {
const group = binding _group ;
// @ts-ignore
binding _group = group [ index ] ;
if ( binding _group === undefined ) {
// @ts-ignore
binding _group = group [ index ] = [ ] ;
}
}
}
binding _group . push ( dom ) ;
dom . addEventListener ( 'change' , ( ) => {
// @ts-ignore
let value = dom . _ _value ;
if ( is _checkbox ) {
value = get _binding _group _value ( binding _group , value , dom . checked ) ;
}
update ( value ) ;
} ) ;
render _effect ( ( ) => {
let value = get _value ( ) ;
if ( is _checkbox ) {
value = value || [ ] ;
// @ts-ignore
dom . checked = value . includes ( dom . _ _value ) ;
} else {
// @ts-ignore
dom . checked = dom . _ _value === value ;
}
} ) ;
render _effect ( ( ) => {
return ( ) => {
const index = binding _group . indexOf ( dom ) ;
if ( index !== - 1 ) {
binding _group . splice ( index , 1 ) ;
}
} ;
} ) ;
}
/ * *
* @ param { HTMLInputElement } dom
* @ param { ( ) => unknown } get _value
* @ param { ( value : unknown ) => void } update
* @ returns { void }
* /
export function bind _checked ( dom , get _value , update ) {
dom . addEventListener ( 'change' , ( ) => {
const value = dom . checked ;
update ( value ) ;
} ) ;
// eslint-disable-next-line eqeqeq
if ( get _value ( ) == undefined ) {
update ( false ) ;
}
render _effect ( ( ) => {
const value = get _value ( ) ;
dom . checked = Boolean ( value ) ;
} ) ;
}
/ * *
* @ param { 'x' | 'y' } type
* @ param { ( ) => number } get _value
* @ param { ( value : number ) => void } update
* @ returns { void }
* /
export function bind _window _scroll ( type , get _value , update ) {
const is _scrolling _x = type === 'x' ;
const target _handler = ( ) => {
scrolling = true ;
clearTimeout ( timeout ) ;
timeout = setTimeout ( clear , 100 ) ;
const value = window [ is _scrolling _x ? 'scrollX' : 'scrollY' ] ;
update ( value ) ;
} ;
addEventListener ( 'scroll' , target _handler , {
passive : true
} ) ;
let latest _value = 0 ;
let scrolling = false ;
/** @type {ReturnType<typeof setTimeout>} */
let timeout ;
const clear = ( ) => {
scrolling = false ;
} ;
render _effect ( ( ) => {
latest _value = get _value ( ) || 0 ;
if ( ! scrolling ) {
scrolling = true ;
clearTimeout ( timeout ) ;
if ( is _scrolling _x ) {
scrollTo ( latest _value , window . scrollY ) ;
} else {
scrollTo ( window . scrollX , latest _value ) ;
}
timeout = setTimeout ( clear , 100 ) ;
}
} ) ;
render _effect ( ( ) => {
return ( ) => {
removeEventListener ( 'scroll' , target _handler ) ;
} ;
} ) ;
}
/ * *
* @ param { string } property
* @ param { string } event _name
* @ param { 'get' | 'set' } type
* @ param { Element } dom
* @ param { ( ) => unknown } get _value
* @ param { ( value : unknown ) => void } update
* @ returns { void }
* /
export function bind _property ( property , event _name , type , dom , get _value , update ) {
const target _handler = ( ) => {
// @ts-ignore
const value = dom [ property ] ;
update ( value ) ;
} ;
dom . addEventListener ( event _name , target _handler ) ;
if ( type === 'set' ) {
render _effect ( ( ) => {
const value = get _value ( ) ;
// @ts-ignore
dom [ property ] = value ;
} ) ;
}
if ( type === 'get' ) {
// @ts-ignore
const value = dom [ property ] ;
update ( value ) ;
}
render _effect ( ( ) => {
// @ts-ignore
if ( dom === document . body || dom === window || dom === document ) {
return ( ) => {
dom . removeEventListener ( event _name , target _handler ) ;
} ;
}
} ) ;
}
/ * *
* Makes an ` export ` ed ( non - prop ) variable available on the ` $ $ props ` object
* so that consumers can do ` bind:x ` on the component .
* @ template V
* @ param { Record < string , unknown > } props
* @ param { string } prop
* @ param { V } value
* @ returns { void }
* /
export function bind _prop ( props , prop , value ) {
const desc = get _descriptor ( props , prop ) ;
if ( desc && desc . set ) {
props [ prop ] = value ;
render _effect ( ( ) => ( ) => {
props [ prop ] = null ;
} ) ;
}
}
/ * *
* @ param { any } bound _value
* @ param { Element } element _or _component
* @ returns { boolean }
* /
function is _bound _this ( bound _value , element _or _component ) {
// Find the original target if the value is proxied.
const proxy _target = bound _value && bound _value [ STATE _SYMBOL ] ? . t ;
return bound _value === element _or _component || proxy _target === element _or _component ;
}
/ * *
* @ param { Element } element _or _component
* @ param { ( value : unknown , ... parts : unknown [ ] ) => void } update
* @ param { ( ... parts : unknown [ ] ) => unknown } get _value
* @ param { ( ) => unknown [ ] } [ get _parts ] Set if the this binding is used inside an each block ,
* returns all the parts of the each block context that are used in the expression
* @ returns { void }
* /
export function bind _this ( element _or _component , update , get _value , get _parts ) {
/** @type {unknown[]} */
let old _parts ;
/** @type {unknown[]} */
let parts ;
const e = effect ( ( ) => {
old _parts = parts ;
// We only track changes to the parts, not the value itself to avoid unnecessary reruns.
parts = get _parts ? . ( ) || [ ] ;
untrack ( ( ) => {
if ( element _or _component !== get _value ( ... parts ) ) {
update ( element _or _component , ... parts ) ;
// If this is an effect rerun (cause: each block context changes), then nullfiy the binding at
// the previous position if it isn't already taken over by a different effect.
if ( old _parts && is _bound _this ( get _value ( ... old _parts ) , element _or _component ) ) {
update ( null , ... old _parts ) ;
}
}
} ) ;
} ) ;
// Add effect teardown (likely causes: if block became false, each item removed, component unmounted).
// In these cases we need to nullify the binding only if we detect that the value is still the same.
// If not, that means that another effect has now taken over the binding.
e . ondestroy = ( ) => {
// Defer to the next tick so that all updates can be reconciled first.
// This solves the case where one variable is shared across multiple this-bindings.
effect ( ( ) => {
if ( parts && is _bound _this ( get _value ( ... parts ) , element _or _component ) ) {
update ( null , ... parts ) ;
}
} ) ;
} ;
}
/ * *
* @ param { Array < string > } events
* @ returns { void }