UNPKG

preact-scoped-model

Version:
428 lines (399 loc) 13.8 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { createNullaryModel: () => createNullaryModel, createPropsSelectorModel: () => createPropsSelectorModel, createReducerModel: () => createReducerModel, createSelector: () => createSelector, createSelectorOnce: () => createSelectorOnce, createSelectors: () => createSelectors, createSnapshot: () => createSnapshot, createStateModel: () => createStateModel, createValue: () => createValue, createValueOnce: () => createValueOnce, default: () => src_default, useScopedModelExists: () => useScopedModelExists, useSelector: () => useSelector, useSelectorOnce: () => useSelectorOnce, useSelectors: () => useSelectors, useSnapshot: () => useSnapshot, useValue: () => useValue, useValueOnce: () => useValueOnce }); module.exports = __toCommonJS(src_exports); // src/create-model.tsx var import_preact = require("preact"); var import_hooks = require("preact/hooks"); var import_compat = require("preact/compat"); var import_preact_hooks = require("@lyonph/preact-hooks"); // src/notifier.tsx var Notifier = class { constructor() { this.alive = true; this.initialized = false; this.listeners = /* @__PURE__ */ new Set(); } subscribe(callback) { if (this.alive) { this.listeners.add(callback); } return () => { if (this.alive) { this.listeners.delete(callback); } }; } consume(value) { if (this.alive) { this.ref = { current: value }; for (const listener of this.listeners.keys()) { listener(value); } } } hydrate(value) { if (this.alive && !this.initialized) { this.ref = { current: value }; } } hasValue() { return !!this.ref; } get value() { if (this.ref == null) { throw new Error("Unexpected missing model reference."); } return this.ref.current; } destroy() { if (this.alive) { this.listeners.clear(); this.alive = false; } } }; // src/utils/id.ts var ID = 0; function generateId() { const current = ID; ID += 1; return current; } // src/utils/MissingScopedModelError.tsx var MissingScopedModelError = class extends Error { constructor(modelName) { super(` The scoped model '${modelName}' is missing from the ancestor component tree. Make sure that the model's Provider is mounted first before trying to access the model's state. `); this.modelName = modelName; } }; // src/create-model.tsx function SHOULD_NOTIFY(a, b) { return !Object.is(a, b); } function createModel(useModelHook, options = {}) { var _a; const context = (0, import_preact.createContext)(null); const id = generateId(); const displayName = options.displayName || `ScopedModel-${id}`; const shouldNotify = (_a = options.shouldNotify) != null ? _a : SHOULD_NOTIFY; const ProcessorInner = (props) => { const emitter = (0, import_hooks.useContext)(context); if (!emitter) { throw new MissingScopedModelError(displayName); } const model = useModelHook(props); emitter.hydrate(model); (0, import_hooks.useEffect)(() => { emitter.initialized = true; }, [emitter]); (0, import_hooks.useEffect)(() => { const hasValue = emitter.hasValue(); if (hasValue && shouldNotify(emitter.value, model) || !hasValue) { emitter.consume(model); } }, [emitter, model]); return null; }; const Processor = (0, import_compat.memo)(ProcessorInner, options.shouldUpdate); const Provider = (_b) => { var _c = _b, { children } = _c, props = __objRest(_c, ["children"]); const emitter = (0, import_preact_hooks.useConstant)(() => new Notifier()); (0, import_hooks.useEffect)(() => () => { emitter.destroy(); }, [emitter]); return /* @__PURE__ */ (0, import_preact.h)(context.Provider, { value: emitter }, /* @__PURE__ */ (0, import_preact.h)(Processor, __spreadValues({}, props)), children); }; Provider.defaultProps = options.defaultProps; if (true) { ProcessorInner.displayName = `ScopedModelProcessor(${displayName}.Processor)`; Processor.displayName = `ScopedModelProcessor(${displayName}.Processor)`; Provider.displayName = `ScopedModel(${displayName})`; context.Provider.displayName = `${displayName}.Provider`; } return { context, Provider, displayName }; } // src/model-factory/create-nullary-model.tsx var NEVER_UPDATE = () => true; function createNullaryModel(useModelHook, options) { return createModel(useModelHook, { displayName: options == null ? void 0 : options.displayName, shouldUpdate: NEVER_UPDATE }); } // src/model-factory/create-state-model.tsx var import_hooks2 = require("preact/hooks"); function createStateModel(initialState, options) { return createNullaryModel(() => (0, import_hooks2.useState)(initialState), options); } // src/model-factory/create-reducer-model.tsx var import_hooks3 = require("preact/hooks"); function createReducerModel(reducer, initialState, options) { return createNullaryModel(() => (0, import_hooks3.useReducer)(reducer, initialState), options); } // src/model-factory/create-props-selector-model.tsx function createPropsSelectorModel(options) { return createModel((props) => props, options); } // src/utils/comparer.ts function defaultCompare(a, b) { return !Object.is(a, b); } function compareList(a, b, compare = defaultCompare) { if (a === b) { return false; } if (a.length !== b.length) { return true; } for (let i = 0; i < a.length; i += 1) { if (compare(a[i], b[i])) { return true; } } return false; } // src/hooks/useSelector.tsx var import_hooks5 = require("preact/hooks"); var import_preact_hooks2 = require("@lyonph/preact-hooks"); // src/hooks/useScopedModelContext.tsx var import_hooks4 = require("preact/hooks"); function useScopedModelContext(model) { const context = (0, import_hooks4.useContext)(model.context); if (!context) { throw new MissingScopedModelError(model.displayName); } return context; } // src/hooks/useSelector.tsx function useSelector(model, selector, shouldUpdate = defaultCompare) { const notifier = useScopedModelContext(model); const sub = (0, import_preact_hooks2.useConditionalMemo)(() => ({ read: () => selector(notifier.value), subscribe: (callback) => notifier.subscribe(callback), shouldUpdate }), { notifier, shouldUpdate }, (prev, next) => prev.notifier !== next.notifier || prev.shouldUpdate !== next.shouldUpdate); const current = (0, import_preact_hooks2.useSubscription)(sub); (0, import_hooks5.useDebugValue)(current); return current; } // src/hook-factory/create-selector-hook.tsx function createSelector(model, selector, shouldUpdate = defaultCompare) { return () => useSelector(model, selector, shouldUpdate); } // src/hooks/useSelectors.tsx var import_hooks6 = require("preact/hooks"); var import_preact_hooks3 = require("@lyonph/preact-hooks"); function useSelectors(model, selector, shouldUpdate = defaultCompare) { const compare = (0, import_preact_hooks3.useConditionalCallback)((a, b) => compareList(a, b, shouldUpdate), shouldUpdate); const value = useSelector(model, selector, compare); (0, import_hooks6.useDebugValue)(value); return value; } // src/hook-factory/create-selectors-hook.tsx function createSelectors(model, selector, shouldUpdate = defaultCompare) { return () => useSelectors(model, selector, shouldUpdate); } // src/hooks/useValue.tsx var import_hooks7 = require("preact/hooks"); var import_preact_hooks4 = require("@lyonph/preact-hooks"); function useValue(model, shouldUpdate = defaultCompare) { const notifier = useScopedModelContext(model); const sub = (0, import_preact_hooks4.useConditionalMemo)(() => ({ read: () => notifier.value, subscribe: (callback) => notifier.subscribe(callback), shouldUpdate }), { notifier, shouldUpdate }, (prev, next) => prev.notifier !== next.notifier || prev.shouldUpdate !== next.shouldUpdate); const current = (0, import_preact_hooks4.useSubscription)(sub); (0, import_hooks7.useDebugValue)(current); return current; } // src/hook-factory/create-value-hook.tsx function createValue(model, shouldUpdate = defaultCompare) { return () => useValue(model, shouldUpdate); } // src/hooks/useValueOnce.tsx var import_hooks8 = require("preact/hooks"); function useValueOnce(model) { const { value } = useScopedModelContext(model); (0, import_hooks8.useDebugValue)(value); return value; } // src/hook-factory/create-value-once-hook.tsx function createValueOnce(model) { return () => useValueOnce(model); } // src/hooks/useSnapshotBase.tsx var import_hooks9 = require("preact/hooks"); function useSnapshotBase(notifier, listener) { (0, import_hooks9.useEffect)(() => { let mounted = true; const callback = (value) => { if (mounted) { listener(value); } }; const unsubscribe = notifier.subscribe(callback); return () => { mounted = false; unsubscribe(); }; }, [notifier, listener]); } // src/hooks/useSnapshot.tsx function useSnapshot(model, listener) { const notifier = useScopedModelContext(model); useSnapshotBase(notifier, listener); } // src/hook-factory/create-snapshot-hook.tsx function createSnapshot(model, listener) { return () => { useSnapshot(model, listener); }; } // src/hooks/useSelectorOnce.tsx var import_hooks10 = require("preact/hooks"); var import_preact_hooks5 = require("@lyonph/preact-hooks"); function useSelectorOnce(model, selector) { const baseValue = useValueOnce(model); const value = (0, import_preact_hooks5.useConditionalMemo)(() => selector(baseValue), { baseValue, selector }, (prev, next) => prev.baseValue !== next.baseValue || prev.selector !== next.selector); (0, import_hooks10.useDebugValue)(value); return value; } // src/hook-factory/create-selector-once-hook.tsx function createSelectorOnce(model, selector) { return () => useSelectorOnce(model, selector); } // src/hooks/useScopedModelExists.tsx var import_hooks11 = require("preact/hooks"); function useScopedModelExists(model) { const context = (0, import_hooks11.useContext)(model.context); return !!context; } // src/index.ts var src_default = createModel; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createNullaryModel, createPropsSelectorModel, createReducerModel, createSelector, createSelectorOnce, createSelectors, createSnapshot, createStateModel, createValue, createValueOnce, useScopedModelExists, useSelector, useSelectorOnce, useSelectors, useSnapshot, useValue, useValueOnce }); /** * @license * MIT License * * Copyright (c) 2021 Alexis Munsayac * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * * @author Alexis Munsayac <alexis.munsayac@gmail.com> * @copyright Alexis Munsayac 2021 */ //# sourceMappingURL=index.js.map