Source: common-sk/modules/stateReflector.js

// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/** @module common-sk/modules/stateReflector */
import * as query from './query';
import * as object from './object';
import { DomReady } from './dom';
/** Track the state of an object and reflect it to and from the URL.
 *
 * @example
 *
 * // If an element has a private variable _state:
 * this._state = {"foo": "bar", "count": 7}
 *
 * // then in the connectedCallback() call:
 * this._stateHasChanged = stateReflector(
 *   () => this._state,
 *   (state) => {
 *     this._state = state;
 *     this._render();
 *   }
 * );
 *
 * // And then any time the app changes the value of _state:
 * this._stateHasChanged();
 *
 * @param getState - Function that returns an object representing the state
 *     we want reflected to the URL.
 *
 * @param setState(o) - Function to call when the URL has changed and the state
 *     object needs to be updated. The object 'o' doesn't need to be copied
 *     as it is a fresh object.
 *
 * @returns A function to call when state has changed and needs to be reflected
 *   to the URL.
 */
export function stateReflector(getState, setState) {
    // The default state of the stateHolder. Used to calculate diffs to state.
    const defaultState = object.deepCopy(getState());
    // Have we done an initial read from the the existing query params.
    let loaded = false;
    // stateFromURL should be called when the URL has changed, it updates
    // the state via setState() and triggers the callback.
    const stateFromURL = () => {
        loaded = true;
        const delta = query.toObject(window.location.search.slice(1), defaultState);
        setState(object.applyDelta(delta, defaultState));
    };
    // When we are loaded we should update the state from the URL.
    DomReady.then(stateFromURL);
    // Every popstate event should also update the state.
    window.addEventListener('popstate', stateFromURL);
    // Return a function to call when the state has changed to force reflection into the URL.
    return () => {
        // Don't overwrite the query params until we have done the initial load from them.
        if (!loaded) {
            return;
        }
        const q = query.fromObject(object.getDelta(getState(), defaultState));
        history.pushState(null, '', window.location.origin + window.location.pathname + '?' + q);
    };
}
//# sourceMappingURL=stateReflector.js.map