All files / src local-storage.js

100% Statements 87/87
100% Branches 16/16
100% Functions 15/15
100% Lines 87/87

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 871x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x
const     string2value = (type, v) =>
{   if( type === 'text')
        return v;
    if( type === 'json')
        try{ return JSON.parse( v );}
        catch(err){ return null }
    const el = document.createElement('input');
    el.setAttribute('type',type);
    if( 'number' === type )
    {   el.value = v;
        return el.valueAsNumber;
    }
    if( 'date' === type )
    {   if(!v) return null;
        el.valueAsDate = new Date( v );
        return el.value;
    }
    el.value = v;
    return el.value;
};
 
let originalSetItem,originalRemoveItem,originalClear;
 
function ensureTrackLocalStorage()
{   if( originalSetItem )
        return;
    originalSetItem = localStorage.setItem;
    localStorage.setItem = function( key, value, ...rest )
        {   originalSetItem.apply(this, [ key, value, ...rest ]);
            window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
        };
    originalRemoveItem = localStorage.removeItem;
    localStorage.removeItem = function( key, ...rest )
        {   originalRemoveItem.apply(this, [ key, ...rest ]);
            window.dispatchEvent( new CustomEvent('local-storage',{detail:{key}}) );
        };
    originalClear = localStorage.clear;
    localStorage.clear = function( ...rest )
        {   originalClear.apply(this, [ ...rest ]);
            window.dispatchEvent( new CustomEvent('local-storage',{detail:{}}) );
        };
}
 
export function localStorageSetItem(key, value)
{   localStorage.setItem(key, value);
    window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
}
export class LocalStorageElement extends HTMLElement
{
    static observedAttributes=
                [   'value' // populated from localStorage, if defined initially, sets the value in storage
                ,   'slice'
                ,   'key'
                ,   'type' // `text|json`, defaults to text, other types are compatible with INPUT field
                ,   'live' // monitors localStorage change
                ];
 
    #value;
    get value(){ return this.#value ===null ? undefined: this.#value }
    set value(o){ return this.#value = o; }
 
    async connectedCallback()
    {
        const    attr = attr => this.getAttribute(attr)
            , fromStorage = ()=>
        {   this.#value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
            this.dispatchEvent( new Event('change') )
        }
        this.#value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
 
        if( this.hasAttribute('value'))
            localStorageSetItem( attr( 'key' ), this.#value = attr( 'value' )  )
        else
            fromStorage()
 
        if( this.hasAttribute('live') )
        {   const listener = (e => (e.detail.key === attr( 'key' ) || !e.detail.key ) && fromStorage());
            window.addEventListener( 'local-storage', listener );
            ensureTrackLocalStorage();
            this._destroy = ()=> window.removeEventListener('local-storage', listener );
        }
    }
    disconnectedCallback(){ this._destroy?.(); }
}
 
window.customElements.define( 'local-storage', LocalStorageElement );
export default LocalStorageElement;