bloc-react
Advanced tools
Comparing version 0.1.27 to 0.1.28
@@ -5,371 +5,380 @@ import React, { useMemo, useContext, useState, useCallback, useEffect } from 'react'; | ||
const cubitDefaultOptions = { | ||
persistKey: "", | ||
persistData: true | ||
persistKey: "", | ||
persistData: true, | ||
}; | ||
const createId = () => { | ||
return "_" + Math.random().toString(36).substr(2, 9); | ||
return '_' + Math.random().toString(36).substr(2, 9); | ||
}; | ||
class BehaviorSubject { | ||
isClosed = false; | ||
prevValue; | ||
value; | ||
observers = []; | ||
constructor(initialValue) { | ||
this.value = initialValue; | ||
} | ||
getValue() { | ||
return this.value; | ||
} | ||
subscribe(observer) { | ||
const id = createId(); | ||
this.observers.push({ observer, id }); | ||
this.triggerObservers(); | ||
return { | ||
unsubscribe: () => this.removeObserver(id) | ||
}; | ||
} | ||
complete() { | ||
this.observers = []; | ||
this.isClosed = true; | ||
} | ||
next(value) { | ||
this.value = value; | ||
this.triggerObservers(); | ||
} | ||
triggerObservers() { | ||
this.observers.forEach(({ observer }) => { | ||
observer.next(this.value); | ||
}); | ||
} | ||
removeObserver(removeId) { | ||
this.observers = this.observers.filter(({ id }) => id !== removeId); | ||
} | ||
isClosed = false; | ||
prevValue; | ||
value; | ||
observers = []; | ||
constructor(initialValue) { | ||
this.value = initialValue; | ||
} | ||
getValue() { | ||
return this.value; | ||
} | ||
subscribe(observer) { | ||
const id = createId(); | ||
this.observers.push({ observer, id }); | ||
this.triggerObservers(); | ||
return { | ||
unsubscribe: () => this.removeObserver(id) | ||
}; | ||
} | ||
complete() { | ||
this.observers = []; | ||
this.isClosed = true; | ||
} | ||
next(value) { | ||
this.value = value; | ||
this.triggerObservers(); | ||
} | ||
triggerObservers() { | ||
this.observers.forEach(({ observer }) => { | ||
observer.next(this.value); | ||
}); | ||
} | ||
removeObserver(removeId) { | ||
this.observers = this.observers.filter(({ id }) => id !== removeId); | ||
} | ||
} | ||
class StreamAbstraction { | ||
isClosed = false; | ||
removeListeners = []; | ||
_options; | ||
_subject; | ||
constructor(initialValue, blocOptions = {}) { | ||
let value = initialValue; | ||
const options = { ...cubitDefaultOptions, ...blocOptions }; | ||
this._options = options; | ||
if (options.persistKey && options.persistData) { | ||
const cachedValue = this.getCachedValue(); | ||
if (!(cachedValue instanceof Error)) { | ||
value = cachedValue; | ||
} | ||
isClosed = false; | ||
removeListeners = []; | ||
_options; | ||
_subject; | ||
constructor(initialValue, blocOptions = {}) { | ||
let value = initialValue; | ||
const options = { ...cubitDefaultOptions, ...blocOptions }; | ||
this._options = options; | ||
if (options.persistKey && options.persistData) { | ||
const cachedValue = this.getCachedValue(); | ||
if (!(cachedValue instanceof Error)) { | ||
value = cachedValue; | ||
} | ||
} | ||
this._subject = new BehaviorSubject(value); | ||
} | ||
this._subject = new BehaviorSubject(value); | ||
} | ||
get state() { | ||
return this._subject.getValue(); | ||
} | ||
removeRemoveListener = (index) => { | ||
this.removeListeners.splice(index, 1); | ||
}; | ||
addRemoveListener = (method) => { | ||
const index = this.removeListeners.length; | ||
this.removeListeners.push(method); | ||
return () => this.removeRemoveListener(index); | ||
}; | ||
subscribe = (observer) => this._subject.subscribe({ | ||
next: observer.next | ||
}); | ||
complete = () => { | ||
this.isClosed = true; | ||
this._subject.complete(); | ||
}; | ||
clearCache = () => { | ||
const key = this._options.persistKey; | ||
if (key) { | ||
localStorage.removeItem(`${LOCAL_STORAGE_PREFIX}${key}`); | ||
get state() { | ||
return this._subject.getValue(); | ||
} | ||
}; | ||
jsonToState(state) { | ||
return JSON.parse(state).state; | ||
} | ||
stateToJson(state) { | ||
return JSON.stringify({ state }); | ||
} | ||
next = (value) => { | ||
this._subject.next(value); | ||
this.updateCache(); | ||
}; | ||
getCachedValue = () => { | ||
const cachedValue = localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${this._options.persistKey}`); | ||
if (cachedValue) { | ||
try { | ||
return this.jsonToState(cachedValue); | ||
} catch (e) { | ||
const error = new Error(`Failed to parse JSON in localstorage for the key: "${LOCAL_STORAGE_PREFIX}${this._options.persistKey}"`); | ||
console.error(error); | ||
return error; | ||
} | ||
removeRemoveListener = (index) => { | ||
this.removeListeners.splice(index, 1); | ||
}; | ||
addRemoveListener = (method) => { | ||
const index = this.removeListeners.length; | ||
this.removeListeners.push(method); | ||
return () => this.removeRemoveListener(index); | ||
}; | ||
subscribe = (observer) => this._subject.subscribe({ | ||
next: observer.next, | ||
}); | ||
complete = () => { | ||
this.isClosed = true; | ||
this._subject.complete(); | ||
}; | ||
clearCache = () => { | ||
const key = this._options.persistKey; | ||
if (key) { | ||
localStorage.removeItem(`${LOCAL_STORAGE_PREFIX}${key}`); | ||
} | ||
}; | ||
jsonToState(state) { | ||
return JSON.parse(state).state; | ||
} | ||
return new Error("Key not found"); | ||
}; | ||
updateCache = () => { | ||
const { persistData, persistKey } = this._options; | ||
if (persistData && persistKey) { | ||
localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${persistKey}`, this.stateToJson(this.state)); | ||
} else { | ||
this.clearCache(); | ||
stateToJson(state) { | ||
return JSON.stringify({ state }); | ||
} | ||
}; | ||
next = (value) => { | ||
this._subject.next(value); | ||
this.updateCache(); | ||
}; | ||
getCachedValue = () => { | ||
const cachedValue = localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${this._options.persistKey}`); | ||
if (cachedValue) { | ||
try { | ||
return this.jsonToState(cachedValue); | ||
} | ||
catch (e) { | ||
const error = new Error(`Failed to parse JSON in localstorage for the key: "${LOCAL_STORAGE_PREFIX}${this._options.persistKey}"`); | ||
console.error(error); | ||
return error; | ||
} | ||
} | ||
return new Error("Key not found"); | ||
}; | ||
updateCache = () => { | ||
const { persistData, persistKey } = this._options; | ||
if (persistData && persistKey) { | ||
localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${persistKey}`, this.stateToJson(this.state)); | ||
} | ||
else { | ||
this.clearCache(); | ||
} | ||
}; | ||
} | ||
class BlocBase extends StreamAbstraction { | ||
id = createId(); | ||
createdAt = new Date(); | ||
meta = { | ||
scope: "unknown" | ||
}; | ||
changeListeners = []; | ||
registerListeners = []; | ||
valueChangeListeners = []; | ||
consumer = null; | ||
constructor(initialValue, blocOptions = {}) { | ||
super(initialValue, blocOptions); | ||
} | ||
removeChangeListener = (index) => { | ||
this.changeListeners.splice(index, 1); | ||
}; | ||
addChangeListener = (method) => { | ||
const index = this.changeListeners.length; | ||
this.changeListeners.push(method); | ||
return () => this.removeChangeListener(index); | ||
}; | ||
removeValueChangeListener = (index) => { | ||
this.valueChangeListeners.splice(index, 1); | ||
}; | ||
addValueChangeListener = (method) => { | ||
const index = this.valueChangeListeners.length; | ||
this.valueChangeListeners.push(method); | ||
return () => this.removeValueChangeListener(index); | ||
}; | ||
removeRegisterListener = (index) => { | ||
this.registerListeners.splice(index, 1); | ||
}; | ||
addRegisterListener = (method) => { | ||
const index = this.registerListeners.length; | ||
this.registerListeners.push(method); | ||
return () => this.removeRegisterListener(index); | ||
}; | ||
notifyChange = (state) => { | ||
this.consumer?.notifyChange(this, state); | ||
this.changeListeners.forEach((fn) => fn({ | ||
currentState: this.state, | ||
nextState: state | ||
}, this)); | ||
}; | ||
notifyValueChange = () => { | ||
this.consumer?.notifyValueChange(this); | ||
this.valueChangeListeners.forEach((fn) => fn(this.state, this)); | ||
}; | ||
id = createId(); | ||
createdAt = new Date(); | ||
meta = { | ||
scope: 'unknown' | ||
}; | ||
changeListeners = []; | ||
registerListeners = []; | ||
valueChangeListeners = []; | ||
consumer = null; | ||
constructor(initialValue, blocOptions = {}) { | ||
super(initialValue, blocOptions); | ||
} | ||
// listeners | ||
removeChangeListener = (index) => { | ||
this.changeListeners.splice(index, 1); | ||
}; | ||
addChangeListener = (method) => { | ||
const index = this.changeListeners.length; | ||
this.changeListeners.push(method); | ||
return () => this.removeChangeListener(index); | ||
}; | ||
removeValueChangeListener = (index) => { | ||
this.valueChangeListeners.splice(index, 1); | ||
}; | ||
addValueChangeListener = (method) => { | ||
const index = this.valueChangeListeners.length; | ||
this.valueChangeListeners.push(method); | ||
return () => this.removeValueChangeListener(index); | ||
}; | ||
removeRegisterListener = (index) => { | ||
this.registerListeners.splice(index, 1); | ||
}; | ||
addRegisterListener = (method) => { | ||
const index = this.registerListeners.length; | ||
this.registerListeners.push(method); | ||
return () => this.removeRegisterListener(index); | ||
}; | ||
notifyChange = (state) => { | ||
this.consumer?.notifyChange(this, state); | ||
this.changeListeners.forEach(fn => fn({ | ||
currentState: this.state, | ||
nextState: state, | ||
}, this)); | ||
}; | ||
notifyValueChange = () => { | ||
this.consumer?.notifyValueChange(this); | ||
this.valueChangeListeners.forEach(fn => fn(this.state, this)); | ||
}; | ||
} | ||
class Bloc extends BlocBase { | ||
onTransition = null; | ||
mapEventToState = null; | ||
constructor(initialState, options) { | ||
super(initialState, options); | ||
} | ||
add = (event) => { | ||
if (this.mapEventToState) { | ||
const newState = this.mapEventToState(event); | ||
this.notifyChange(newState); | ||
this.notifyTransition(newState, event); | ||
this.next(newState); | ||
this.notifyValueChange(); | ||
} else { | ||
console.error(`"mapEventToState" not implemented for "${this.constructor.name}"`); | ||
onTransition = null; | ||
mapEventToState = null; | ||
constructor(initialState, options) { | ||
super(initialState, options); | ||
} | ||
}; | ||
notifyTransition = (state, event) => { | ||
this.consumer?.notifyTransition(this, state, event); | ||
this.onTransition?.({ | ||
currentState: this.state, | ||
event, | ||
nextState: state | ||
}); | ||
}; | ||
add = (event) => { | ||
if (this.mapEventToState) { | ||
const newState = this.mapEventToState(event); | ||
this.notifyChange(newState); | ||
this.notifyTransition(newState, event); | ||
this.next(newState); | ||
this.notifyValueChange(); | ||
} | ||
else { | ||
console.error(`"mapEventToState" not implemented for "${this.constructor.name}"`); | ||
} | ||
}; | ||
notifyTransition = (state, event) => { | ||
this.consumer?.notifyTransition(this, state, event); | ||
this.onTransition?.({ | ||
currentState: this.state, | ||
event, | ||
nextState: state, | ||
}); | ||
}; | ||
} | ||
class Cubit extends BlocBase { | ||
emit = (value) => { | ||
this.notifyChange(value); | ||
this.next(value); | ||
this.notifyValueChange(); | ||
}; | ||
emit = (value) => { | ||
this.notifyChange(value); | ||
this.next(value); | ||
this.notifyValueChange(); | ||
}; | ||
} | ||
class BlocObserver { | ||
onChange; | ||
onTransition; | ||
constructor(methods = {}) { | ||
this.onChange = methods.onChange ? methods.onChange : this.defaultAction; | ||
this.onTransition = methods.onTransition ? methods.onTransition : this.defaultAction; | ||
} | ||
addChange = (bloc, state) => { | ||
this.onChange(bloc, this.createChangeEvent(bloc, state)); | ||
}; | ||
addTransition = (bloc, state, event) => { | ||
this.onTransition(bloc, this.createTransitionEvent(bloc, state, event)); | ||
}; | ||
addBlocAdded = (bloc) => { | ||
this.onBlocAdded(bloc); | ||
}; | ||
addBlocRemoved = (bloc) => { | ||
this.onBlocRemoved(bloc); | ||
}; | ||
defaultAction = () => { | ||
}; | ||
onBlocAdded = this.defaultAction; | ||
onBlocRemoved = this.defaultAction; | ||
createTransitionEvent(bloc, state, event) { | ||
return { | ||
currentState: bloc.state, | ||
event, | ||
nextState: state | ||
onChange; | ||
onTransition; | ||
constructor(methods = {}) { | ||
this.onChange = methods.onChange ? methods.onChange : this.defaultAction; | ||
this.onTransition = methods.onTransition ? methods.onTransition : this.defaultAction; | ||
} | ||
// trigger events | ||
addChange = (bloc, state) => { | ||
this.onChange(bloc, this.createChangeEvent(bloc, state)); | ||
}; | ||
} | ||
createChangeEvent(bloc, state) { | ||
return { | ||
currentState: bloc.state, | ||
nextState: state | ||
addTransition = (bloc, state, event) => { | ||
this.onTransition(bloc, this.createTransitionEvent(bloc, state, event)); | ||
}; | ||
} | ||
addBlocAdded = (bloc) => { | ||
this.onBlocAdded(bloc); | ||
}; | ||
addBlocRemoved = (bloc) => { | ||
this.onBlocRemoved(bloc); | ||
}; | ||
// consume | ||
defaultAction = () => { }; | ||
onBlocAdded = this.defaultAction; | ||
onBlocRemoved = this.defaultAction; | ||
createTransitionEvent(bloc, state, event) { | ||
return { | ||
currentState: bloc.state, | ||
event, | ||
nextState: state, | ||
}; | ||
} | ||
createChangeEvent(bloc, state) { | ||
return { | ||
currentState: bloc.state, | ||
nextState: state, | ||
}; | ||
} | ||
} | ||
class BlocConsumer { | ||
observer; | ||
mocksEnabled = false; | ||
providerList = []; | ||
blocListGlobal; | ||
blocChangeObservers = []; | ||
blocValueChangeObservers = []; | ||
mockBlocs = []; | ||
constructor(blocs, options = {}) { | ||
this.blocListGlobal = blocs; | ||
this.observer = options.observer || new BlocObserver(); | ||
for (const b of blocs) { | ||
b.consumer = this; | ||
b.registerListeners.forEach((fn) => fn(this, b)); | ||
b.meta.scope = "global"; | ||
this.observer.addBlocAdded(b); | ||
observer; | ||
mocksEnabled = false; | ||
providerList = []; | ||
blocListGlobal; | ||
blocChangeObservers = []; | ||
blocValueChangeObservers = []; | ||
mockBlocs = []; | ||
constructor(blocs, options = {}) { | ||
this.blocListGlobal = blocs; | ||
this.observer = options.observer || new BlocObserver(); | ||
for (const b of blocs) { | ||
b.consumer = this; | ||
b.registerListeners.forEach(fn => fn(this, b)); | ||
b.meta.scope = 'global'; | ||
this.observer.addBlocAdded(b); | ||
} | ||
} | ||
} | ||
notifyChange(bloc, state) { | ||
if (bloc.isClosed) { | ||
return; | ||
notifyChange(bloc, state) { | ||
if (bloc.isClosed) { | ||
return; | ||
} | ||
this.observer.addChange(bloc, state); | ||
for (const [blocClass, callback, scope] of this.blocChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || | ||
(isGlobal && scope === "global") || | ||
(!isGlobal && scope === "local"); | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc, { | ||
nextState: state, | ||
currentState: bloc.state | ||
}); | ||
} | ||
} | ||
} | ||
this.observer.addChange(bloc, state); | ||
for (const [blocClass, callback, scope] of this.blocChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || isGlobal && scope === "global" || !isGlobal && scope === "local"; | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc, { | ||
nextState: state, | ||
currentState: bloc.state | ||
}); | ||
} | ||
notifyValueChange(bloc) { | ||
if (bloc.isClosed) { | ||
return; | ||
} | ||
for (const [blocClass, callback, scope] of this.blocValueChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || | ||
(isGlobal && scope === "global") || | ||
(!isGlobal && scope === "local"); | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc); | ||
} | ||
} | ||
} | ||
} | ||
notifyValueChange(bloc) { | ||
if (bloc.isClosed) { | ||
return; | ||
notifyTransition(bloc, state, event) { | ||
if (bloc.isClosed) { | ||
return; | ||
} | ||
this.observer.addTransition(bloc, state, event); | ||
} | ||
for (const [blocClass, callback, scope] of this.blocValueChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || isGlobal && scope === "global" || !isGlobal && scope === "local"; | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc); | ||
} | ||
addBlocChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
} | ||
notifyTransition(bloc, state, event) { | ||
if (bloc.isClosed) { | ||
return; | ||
addBlocValueChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocValueChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
this.observer.addTransition(bloc, state, event); | ||
} | ||
addBlocChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
addBlocValueChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocValueChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
addLocalBloc(item) { | ||
this.providerList.push(item); | ||
item.bloc.consumer = this; | ||
item.bloc.registerListeners.forEach((fn) => fn(this, item.bloc)); | ||
item.bloc.meta.scope = "local"; | ||
this.observer.addBlocAdded(item.bloc); | ||
} | ||
removeLocalBloc(id, bloc) { | ||
const item = this.providerList.find((i) => i.id === id && i.bloc === bloc); | ||
if (item) { | ||
item.bloc.complete(); | ||
item.bloc.removeListeners.forEach((fn) => fn()); | ||
this.observer.addBlocRemoved(item.bloc); | ||
this.providerList = this.providerList.filter((i) => i !== item); | ||
addLocalBloc(item) { | ||
this.providerList.push(item); | ||
item.bloc.consumer = this; | ||
item.bloc.registerListeners.forEach(fn => fn(this, item.bloc)); | ||
item.bloc.meta.scope = 'local'; | ||
this.observer.addBlocAdded(item.bloc); | ||
} | ||
} | ||
addBlocMock(bloc) { | ||
if (this.mocksEnabled) { | ||
this.mockBlocs = [bloc, ...this.mockBlocs]; | ||
removeLocalBloc(id, bloc) { | ||
const item = this.providerList.find(i => i.id === id && i.bloc === bloc); | ||
if (item) { | ||
item.bloc.complete(); | ||
item.bloc.removeListeners.forEach(fn => fn()); | ||
this.observer.addBlocRemoved(item.bloc); | ||
this.providerList = this.providerList.filter(i => i !== item); | ||
} | ||
} | ||
} | ||
resetMocks() { | ||
this.mockBlocs = []; | ||
} | ||
getGlobalBloc(blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
addBlocMock(bloc) { | ||
if (this.mocksEnabled) { | ||
this.mockBlocs = [bloc, ...this.mockBlocs]; | ||
} | ||
} | ||
return this.blocListGlobal.find((c) => c instanceof blocClass); | ||
} | ||
getLocalBlocForProvider(id, blocClass) { | ||
for (const providerItem of this.providerList) { | ||
if (providerItem.id === id) { | ||
if (providerItem.bloc instanceof blocClass) { | ||
return providerItem.bloc; | ||
resetMocks() { | ||
this.mockBlocs = []; | ||
} | ||
getGlobalBloc(blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
} | ||
let parent = providerItem.parent; | ||
while (parent) { | ||
const parentItem = this.providerList.find((i) => i.id === parent); | ||
if (parentItem?.bloc instanceof blocClass) { | ||
return parentItem.bloc; | ||
} | ||
parent = parentItem?.parent; | ||
return this.blocListGlobal.find(c => c instanceof blocClass); | ||
} | ||
getLocalBlocForProvider(id, blocClass) { | ||
for (const providerItem of this.providerList) { | ||
if (providerItem.id === id) { | ||
if (providerItem.bloc instanceof blocClass) { | ||
return providerItem.bloc; | ||
} | ||
let parent = providerItem.parent; | ||
while (parent) { | ||
const parentItem = this.providerList.find(i => i.id === parent); | ||
if (parentItem?.bloc instanceof blocClass) { | ||
return parentItem.bloc; | ||
} | ||
parent = parentItem?.parent; | ||
} | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
return void 0; | ||
} | ||
getGlobalBlocInstance(global, blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
getGlobalBlocInstance(global, blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
} | ||
return global.find((c) => c instanceof blocClass); | ||
} | ||
return global.find((c) => c instanceof blocClass); | ||
} | ||
} | ||
const defaultBlocHookOptions = { | ||
subscribe: true | ||
subscribe: true | ||
}; | ||
class BlocRuntimeError { | ||
error; | ||
constructor(message) { | ||
this.error = new Error(message); | ||
} | ||
error; | ||
constructor(message) { | ||
this.error = new Error(message); | ||
} | ||
} | ||
@@ -379,26 +388,26 @@ class NoValue { | ||
class BlocReact extends BlocConsumer { | ||
providerCount = 0; | ||
_blocsGlobal; | ||
_contextLocalProviderKey = React.createContext("none"); | ||
constructor(blocs, options) { | ||
super(blocs, options); | ||
this._blocsGlobal = blocs; | ||
this.BlocProvider = this.BlocProvider.bind(this); | ||
this.BlocBuilder = this.BlocBuilder.bind(this); | ||
} | ||
useBloc = (blocClass, options = {}) => { | ||
const mergedOptions = { | ||
...defaultBlocHookOptions, | ||
...options | ||
}; | ||
let blocInstance = useMemo(() => options.create ? options.create() : void 0, []); | ||
if (!blocInstance) { | ||
const localProviderKey = useContext(this._contextLocalProviderKey); | ||
const localBlocInstance = useMemo(() => this.getLocalBlocForProvider(localProviderKey, blocClass), []); | ||
blocInstance = useMemo(() => localBlocInstance || this.getGlobalBlocInstance(this._blocsGlobal, blocClass), []); | ||
providerCount = 0; | ||
_blocsGlobal; | ||
_contextLocalProviderKey = React.createContext('none'); | ||
constructor(blocs, options) { | ||
super(blocs, options); | ||
this._blocsGlobal = blocs; | ||
this.BlocProvider = this.BlocProvider.bind(this); | ||
this.BlocBuilder = this.BlocBuilder.bind(this); | ||
} | ||
const { subscribe, shouldUpdate = true } = mergedOptions; | ||
if (!blocInstance) { | ||
const name = blocClass.prototype.constructor.name; | ||
const error = new BlocRuntimeError(`"${name}" | ||
useBloc = (blocClass, options = {}) => { | ||
const mergedOptions = { | ||
...defaultBlocHookOptions, | ||
...options | ||
}; | ||
let blocInstance = useMemo(() => options.create ? options.create() : undefined, []); | ||
if (!blocInstance) { | ||
const localProviderKey = useContext(this._contextLocalProviderKey); | ||
const localBlocInstance = useMemo(() => this.getLocalBlocForProvider(localProviderKey, blocClass), []); | ||
blocInstance = useMemo(() => localBlocInstance || this.getGlobalBlocInstance(this._blocsGlobal, blocClass), []); | ||
} | ||
const { subscribe, shouldUpdate = true } = mergedOptions; | ||
if (!blocInstance) { | ||
const name = blocClass.prototype.constructor.name; | ||
const error = new BlocRuntimeError(`"${name}" | ||
no bloc with this name was found in the global context. | ||
@@ -418,81 +427,79 @@ | ||
`); | ||
console.error(error.error); | ||
return [ | ||
NoValue, | ||
{}, | ||
{ | ||
error, | ||
complete: true | ||
console.error(error.error); | ||
return [ | ||
NoValue, | ||
{}, | ||
{ | ||
error, | ||
complete: true | ||
} | ||
]; | ||
} | ||
]; | ||
const [data, setData] = useState(blocInstance.state); | ||
const updateData = useCallback((nextState) => { | ||
if (shouldUpdate === true || shouldUpdate({ nextState, currentState: data })) { | ||
setData(nextState); | ||
} | ||
}, []); | ||
useEffect(() => { | ||
if (subscribe) { | ||
const subscription = blocInstance?.subscribe({ | ||
next: updateData | ||
}); | ||
return () => { | ||
subscription?.unsubscribe(); | ||
}; | ||
} | ||
}, []); | ||
return [ | ||
data, | ||
blocInstance | ||
]; | ||
}; | ||
// Components | ||
BlocBuilder(props) { | ||
const hook = this.useBloc(props.blocClass, { | ||
shouldUpdate: props.shouldUpdate | ||
}); | ||
return props.builder(hook); | ||
} | ||
const [data, setData] = useState(blocInstance.state); | ||
const updateData = useCallback((nextState) => { | ||
if (shouldUpdate === true || shouldUpdate({ nextState, currentState: data })) { | ||
setData(nextState); | ||
} | ||
}, []); | ||
useEffect(() => { | ||
if (subscribe) { | ||
const subscription = blocInstance?.subscribe({ | ||
next: updateData | ||
}); | ||
return () => { | ||
subscription?.unsubscribe(); | ||
; | ||
BlocProvider(props) { | ||
const id = useMemo(() => createId(), []); | ||
const localProviderKey = useContext(this._contextLocalProviderKey); | ||
const bloc = useMemo(() => { | ||
const newBloc = typeof props.bloc === "function" ? props.bloc(id) : props.bloc; | ||
if (newBloc) { | ||
this.addLocalBloc({ | ||
bloc: newBloc, | ||
id, | ||
parent: localProviderKey | ||
}); | ||
} | ||
else { | ||
console.error(`BLoC is undefined`); | ||
} | ||
return newBloc; | ||
}, []); | ||
const context = useMemo(() => { | ||
return React.createContext(bloc); | ||
}, [bloc]); | ||
useEffect(() => { | ||
return () => { | ||
this.removeLocalBloc(id, bloc); | ||
}; | ||
}, []); | ||
return (React.createElement(this._contextLocalProviderKey.Provider, { value: id }, | ||
React.createElement(context.Provider, { value: bloc }, props.children))); | ||
} | ||
; | ||
withBlocProvider = (bloc) => (Component) => { | ||
const { BlocProvider } = this; | ||
return class WithBlocProvider extends React.Component { | ||
render() { | ||
return (React.createElement(BlocProvider, { bloc: bloc }, | ||
React.createElement(Component, { ...this.props }))); | ||
} | ||
}; | ||
} | ||
}, []); | ||
return [ | ||
data, | ||
blocInstance | ||
]; | ||
}; | ||
BlocBuilder(props) { | ||
const hook = this.useBloc(props.blocClass, { | ||
shouldUpdate: props.shouldUpdate | ||
}); | ||
return props.builder(hook); | ||
} | ||
BlocProvider(props) { | ||
const id = useMemo(() => createId(), []); | ||
const localProviderKey = useContext(this._contextLocalProviderKey); | ||
const bloc = useMemo(() => { | ||
const newBloc = typeof props.bloc === "function" ? props.bloc(id) : props.bloc; | ||
if (newBloc) { | ||
this.addLocalBloc({ | ||
bloc: newBloc, | ||
id, | ||
parent: localProviderKey | ||
}); | ||
} else { | ||
console.error(`BLoC is undefined`); | ||
} | ||
return newBloc; | ||
}, []); | ||
const context = useMemo(() => { | ||
return React.createContext(bloc); | ||
}, [bloc]); | ||
useEffect(() => { | ||
return () => { | ||
this.removeLocalBloc(id, bloc); | ||
}; | ||
}, []); | ||
return /* @__PURE__ */ React.createElement(this._contextLocalProviderKey.Provider, { | ||
value: id | ||
}, /* @__PURE__ */ React.createElement(context.Provider, { | ||
value: bloc | ||
}, props.children)); | ||
} | ||
withBlocProvider = (bloc) => (Component) => { | ||
const { BlocProvider } = this; | ||
return class WithBlocProvider extends React.Component { | ||
render() { | ||
return /* @__PURE__ */ React.createElement(BlocProvider, { | ||
bloc | ||
}, /* @__PURE__ */ React.createElement(Component, { | ||
...this.props | ||
})); | ||
} | ||
}; | ||
}; | ||
} | ||
@@ -499,0 +506,0 @@ |
@@ -13,371 +13,380 @@ 'use strict'; | ||
const cubitDefaultOptions = { | ||
persistKey: "", | ||
persistData: true | ||
persistKey: "", | ||
persistData: true, | ||
}; | ||
const createId = () => { | ||
return "_" + Math.random().toString(36).substr(2, 9); | ||
return '_' + Math.random().toString(36).substr(2, 9); | ||
}; | ||
class BehaviorSubject { | ||
isClosed = false; | ||
prevValue; | ||
value; | ||
observers = []; | ||
constructor(initialValue) { | ||
this.value = initialValue; | ||
} | ||
getValue() { | ||
return this.value; | ||
} | ||
subscribe(observer) { | ||
const id = createId(); | ||
this.observers.push({ observer, id }); | ||
this.triggerObservers(); | ||
return { | ||
unsubscribe: () => this.removeObserver(id) | ||
}; | ||
} | ||
complete() { | ||
this.observers = []; | ||
this.isClosed = true; | ||
} | ||
next(value) { | ||
this.value = value; | ||
this.triggerObservers(); | ||
} | ||
triggerObservers() { | ||
this.observers.forEach(({ observer }) => { | ||
observer.next(this.value); | ||
}); | ||
} | ||
removeObserver(removeId) { | ||
this.observers = this.observers.filter(({ id }) => id !== removeId); | ||
} | ||
isClosed = false; | ||
prevValue; | ||
value; | ||
observers = []; | ||
constructor(initialValue) { | ||
this.value = initialValue; | ||
} | ||
getValue() { | ||
return this.value; | ||
} | ||
subscribe(observer) { | ||
const id = createId(); | ||
this.observers.push({ observer, id }); | ||
this.triggerObservers(); | ||
return { | ||
unsubscribe: () => this.removeObserver(id) | ||
}; | ||
} | ||
complete() { | ||
this.observers = []; | ||
this.isClosed = true; | ||
} | ||
next(value) { | ||
this.value = value; | ||
this.triggerObservers(); | ||
} | ||
triggerObservers() { | ||
this.observers.forEach(({ observer }) => { | ||
observer.next(this.value); | ||
}); | ||
} | ||
removeObserver(removeId) { | ||
this.observers = this.observers.filter(({ id }) => id !== removeId); | ||
} | ||
} | ||
class StreamAbstraction { | ||
isClosed = false; | ||
removeListeners = []; | ||
_options; | ||
_subject; | ||
constructor(initialValue, blocOptions = {}) { | ||
let value = initialValue; | ||
const options = { ...cubitDefaultOptions, ...blocOptions }; | ||
this._options = options; | ||
if (options.persistKey && options.persistData) { | ||
const cachedValue = this.getCachedValue(); | ||
if (!(cachedValue instanceof Error)) { | ||
value = cachedValue; | ||
} | ||
isClosed = false; | ||
removeListeners = []; | ||
_options; | ||
_subject; | ||
constructor(initialValue, blocOptions = {}) { | ||
let value = initialValue; | ||
const options = { ...cubitDefaultOptions, ...blocOptions }; | ||
this._options = options; | ||
if (options.persistKey && options.persistData) { | ||
const cachedValue = this.getCachedValue(); | ||
if (!(cachedValue instanceof Error)) { | ||
value = cachedValue; | ||
} | ||
} | ||
this._subject = new BehaviorSubject(value); | ||
} | ||
this._subject = new BehaviorSubject(value); | ||
} | ||
get state() { | ||
return this._subject.getValue(); | ||
} | ||
removeRemoveListener = (index) => { | ||
this.removeListeners.splice(index, 1); | ||
}; | ||
addRemoveListener = (method) => { | ||
const index = this.removeListeners.length; | ||
this.removeListeners.push(method); | ||
return () => this.removeRemoveListener(index); | ||
}; | ||
subscribe = (observer) => this._subject.subscribe({ | ||
next: observer.next | ||
}); | ||
complete = () => { | ||
this.isClosed = true; | ||
this._subject.complete(); | ||
}; | ||
clearCache = () => { | ||
const key = this._options.persistKey; | ||
if (key) { | ||
localStorage.removeItem(`${LOCAL_STORAGE_PREFIX}${key}`); | ||
get state() { | ||
return this._subject.getValue(); | ||
} | ||
}; | ||
jsonToState(state) { | ||
return JSON.parse(state).state; | ||
} | ||
stateToJson(state) { | ||
return JSON.stringify({ state }); | ||
} | ||
next = (value) => { | ||
this._subject.next(value); | ||
this.updateCache(); | ||
}; | ||
getCachedValue = () => { | ||
const cachedValue = localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${this._options.persistKey}`); | ||
if (cachedValue) { | ||
try { | ||
return this.jsonToState(cachedValue); | ||
} catch (e) { | ||
const error = new Error(`Failed to parse JSON in localstorage for the key: "${LOCAL_STORAGE_PREFIX}${this._options.persistKey}"`); | ||
console.error(error); | ||
return error; | ||
} | ||
removeRemoveListener = (index) => { | ||
this.removeListeners.splice(index, 1); | ||
}; | ||
addRemoveListener = (method) => { | ||
const index = this.removeListeners.length; | ||
this.removeListeners.push(method); | ||
return () => this.removeRemoveListener(index); | ||
}; | ||
subscribe = (observer) => this._subject.subscribe({ | ||
next: observer.next, | ||
}); | ||
complete = () => { | ||
this.isClosed = true; | ||
this._subject.complete(); | ||
}; | ||
clearCache = () => { | ||
const key = this._options.persistKey; | ||
if (key) { | ||
localStorage.removeItem(`${LOCAL_STORAGE_PREFIX}${key}`); | ||
} | ||
}; | ||
jsonToState(state) { | ||
return JSON.parse(state).state; | ||
} | ||
return new Error("Key not found"); | ||
}; | ||
updateCache = () => { | ||
const { persistData, persistKey } = this._options; | ||
if (persistData && persistKey) { | ||
localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${persistKey}`, this.stateToJson(this.state)); | ||
} else { | ||
this.clearCache(); | ||
stateToJson(state) { | ||
return JSON.stringify({ state }); | ||
} | ||
}; | ||
next = (value) => { | ||
this._subject.next(value); | ||
this.updateCache(); | ||
}; | ||
getCachedValue = () => { | ||
const cachedValue = localStorage.getItem(`${LOCAL_STORAGE_PREFIX}${this._options.persistKey}`); | ||
if (cachedValue) { | ||
try { | ||
return this.jsonToState(cachedValue); | ||
} | ||
catch (e) { | ||
const error = new Error(`Failed to parse JSON in localstorage for the key: "${LOCAL_STORAGE_PREFIX}${this._options.persistKey}"`); | ||
console.error(error); | ||
return error; | ||
} | ||
} | ||
return new Error("Key not found"); | ||
}; | ||
updateCache = () => { | ||
const { persistData, persistKey } = this._options; | ||
if (persistData && persistKey) { | ||
localStorage.setItem(`${LOCAL_STORAGE_PREFIX}${persistKey}`, this.stateToJson(this.state)); | ||
} | ||
else { | ||
this.clearCache(); | ||
} | ||
}; | ||
} | ||
class BlocBase extends StreamAbstraction { | ||
id = createId(); | ||
createdAt = new Date(); | ||
meta = { | ||
scope: "unknown" | ||
}; | ||
changeListeners = []; | ||
registerListeners = []; | ||
valueChangeListeners = []; | ||
consumer = null; | ||
constructor(initialValue, blocOptions = {}) { | ||
super(initialValue, blocOptions); | ||
} | ||
removeChangeListener = (index) => { | ||
this.changeListeners.splice(index, 1); | ||
}; | ||
addChangeListener = (method) => { | ||
const index = this.changeListeners.length; | ||
this.changeListeners.push(method); | ||
return () => this.removeChangeListener(index); | ||
}; | ||
removeValueChangeListener = (index) => { | ||
this.valueChangeListeners.splice(index, 1); | ||
}; | ||
addValueChangeListener = (method) => { | ||
const index = this.valueChangeListeners.length; | ||
this.valueChangeListeners.push(method); | ||
return () => this.removeValueChangeListener(index); | ||
}; | ||
removeRegisterListener = (index) => { | ||
this.registerListeners.splice(index, 1); | ||
}; | ||
addRegisterListener = (method) => { | ||
const index = this.registerListeners.length; | ||
this.registerListeners.push(method); | ||
return () => this.removeRegisterListener(index); | ||
}; | ||
notifyChange = (state) => { | ||
this.consumer?.notifyChange(this, state); | ||
this.changeListeners.forEach((fn) => fn({ | ||
currentState: this.state, | ||
nextState: state | ||
}, this)); | ||
}; | ||
notifyValueChange = () => { | ||
this.consumer?.notifyValueChange(this); | ||
this.valueChangeListeners.forEach((fn) => fn(this.state, this)); | ||
}; | ||
id = createId(); | ||
createdAt = new Date(); | ||
meta = { | ||
scope: 'unknown' | ||
}; | ||
changeListeners = []; | ||
registerListeners = []; | ||
valueChangeListeners = []; | ||
consumer = null; | ||
constructor(initialValue, blocOptions = {}) { | ||
super(initialValue, blocOptions); | ||
} | ||
// listeners | ||
removeChangeListener = (index) => { | ||
this.changeListeners.splice(index, 1); | ||
}; | ||
addChangeListener = (method) => { | ||
const index = this.changeListeners.length; | ||
this.changeListeners.push(method); | ||
return () => this.removeChangeListener(index); | ||
}; | ||
removeValueChangeListener = (index) => { | ||
this.valueChangeListeners.splice(index, 1); | ||
}; | ||
addValueChangeListener = (method) => { | ||
const index = this.valueChangeListeners.length; | ||
this.valueChangeListeners.push(method); | ||
return () => this.removeValueChangeListener(index); | ||
}; | ||
removeRegisterListener = (index) => { | ||
this.registerListeners.splice(index, 1); | ||
}; | ||
addRegisterListener = (method) => { | ||
const index = this.registerListeners.length; | ||
this.registerListeners.push(method); | ||
return () => this.removeRegisterListener(index); | ||
}; | ||
notifyChange = (state) => { | ||
this.consumer?.notifyChange(this, state); | ||
this.changeListeners.forEach(fn => fn({ | ||
currentState: this.state, | ||
nextState: state, | ||
}, this)); | ||
}; | ||
notifyValueChange = () => { | ||
this.consumer?.notifyValueChange(this); | ||
this.valueChangeListeners.forEach(fn => fn(this.state, this)); | ||
}; | ||
} | ||
class Bloc extends BlocBase { | ||
onTransition = null; | ||
mapEventToState = null; | ||
constructor(initialState, options) { | ||
super(initialState, options); | ||
} | ||
add = (event) => { | ||
if (this.mapEventToState) { | ||
const newState = this.mapEventToState(event); | ||
this.notifyChange(newState); | ||
this.notifyTransition(newState, event); | ||
this.next(newState); | ||
this.notifyValueChange(); | ||
} else { | ||
console.error(`"mapEventToState" not implemented for "${this.constructor.name}"`); | ||
onTransition = null; | ||
mapEventToState = null; | ||
constructor(initialState, options) { | ||
super(initialState, options); | ||
} | ||
}; | ||
notifyTransition = (state, event) => { | ||
this.consumer?.notifyTransition(this, state, event); | ||
this.onTransition?.({ | ||
currentState: this.state, | ||
event, | ||
nextState: state | ||
}); | ||
}; | ||
add = (event) => { | ||
if (this.mapEventToState) { | ||
const newState = this.mapEventToState(event); | ||
this.notifyChange(newState); | ||
this.notifyTransition(newState, event); | ||
this.next(newState); | ||
this.notifyValueChange(); | ||
} | ||
else { | ||
console.error(`"mapEventToState" not implemented for "${this.constructor.name}"`); | ||
} | ||
}; | ||
notifyTransition = (state, event) => { | ||
this.consumer?.notifyTransition(this, state, event); | ||
this.onTransition?.({ | ||
currentState: this.state, | ||
event, | ||
nextState: state, | ||
}); | ||
}; | ||
} | ||
class Cubit extends BlocBase { | ||
emit = (value) => { | ||
this.notifyChange(value); | ||
this.next(value); | ||
this.notifyValueChange(); | ||
}; | ||
emit = (value) => { | ||
this.notifyChange(value); | ||
this.next(value); | ||
this.notifyValueChange(); | ||
}; | ||
} | ||
class BlocObserver { | ||
onChange; | ||
onTransition; | ||
constructor(methods = {}) { | ||
this.onChange = methods.onChange ? methods.onChange : this.defaultAction; | ||
this.onTransition = methods.onTransition ? methods.onTransition : this.defaultAction; | ||
} | ||
addChange = (bloc, state) => { | ||
this.onChange(bloc, this.createChangeEvent(bloc, state)); | ||
}; | ||
addTransition = (bloc, state, event) => { | ||
this.onTransition(bloc, this.createTransitionEvent(bloc, state, event)); | ||
}; | ||
addBlocAdded = (bloc) => { | ||
this.onBlocAdded(bloc); | ||
}; | ||
addBlocRemoved = (bloc) => { | ||
this.onBlocRemoved(bloc); | ||
}; | ||
defaultAction = () => { | ||
}; | ||
onBlocAdded = this.defaultAction; | ||
onBlocRemoved = this.defaultAction; | ||
createTransitionEvent(bloc, state, event) { | ||
return { | ||
currentState: bloc.state, | ||
event, | ||
nextState: state | ||
onChange; | ||
onTransition; | ||
constructor(methods = {}) { | ||
this.onChange = methods.onChange ? methods.onChange : this.defaultAction; | ||
this.onTransition = methods.onTransition ? methods.onTransition : this.defaultAction; | ||
} | ||
// trigger events | ||
addChange = (bloc, state) => { | ||
this.onChange(bloc, this.createChangeEvent(bloc, state)); | ||
}; | ||
} | ||
createChangeEvent(bloc, state) { | ||
return { | ||
currentState: bloc.state, | ||
nextState: state | ||
addTransition = (bloc, state, event) => { | ||
this.onTransition(bloc, this.createTransitionEvent(bloc, state, event)); | ||
}; | ||
} | ||
addBlocAdded = (bloc) => { | ||
this.onBlocAdded(bloc); | ||
}; | ||
addBlocRemoved = (bloc) => { | ||
this.onBlocRemoved(bloc); | ||
}; | ||
// consume | ||
defaultAction = () => { }; | ||
onBlocAdded = this.defaultAction; | ||
onBlocRemoved = this.defaultAction; | ||
createTransitionEvent(bloc, state, event) { | ||
return { | ||
currentState: bloc.state, | ||
event, | ||
nextState: state, | ||
}; | ||
} | ||
createChangeEvent(bloc, state) { | ||
return { | ||
currentState: bloc.state, | ||
nextState: state, | ||
}; | ||
} | ||
} | ||
class BlocConsumer { | ||
observer; | ||
mocksEnabled = false; | ||
providerList = []; | ||
blocListGlobal; | ||
blocChangeObservers = []; | ||
blocValueChangeObservers = []; | ||
mockBlocs = []; | ||
constructor(blocs, options = {}) { | ||
this.blocListGlobal = blocs; | ||
this.observer = options.observer || new BlocObserver(); | ||
for (const b of blocs) { | ||
b.consumer = this; | ||
b.registerListeners.forEach((fn) => fn(this, b)); | ||
b.meta.scope = "global"; | ||
this.observer.addBlocAdded(b); | ||
observer; | ||
mocksEnabled = false; | ||
providerList = []; | ||
blocListGlobal; | ||
blocChangeObservers = []; | ||
blocValueChangeObservers = []; | ||
mockBlocs = []; | ||
constructor(blocs, options = {}) { | ||
this.blocListGlobal = blocs; | ||
this.observer = options.observer || new BlocObserver(); | ||
for (const b of blocs) { | ||
b.consumer = this; | ||
b.registerListeners.forEach(fn => fn(this, b)); | ||
b.meta.scope = 'global'; | ||
this.observer.addBlocAdded(b); | ||
} | ||
} | ||
} | ||
notifyChange(bloc, state) { | ||
if (bloc.isClosed) { | ||
return; | ||
notifyChange(bloc, state) { | ||
if (bloc.isClosed) { | ||
return; | ||
} | ||
this.observer.addChange(bloc, state); | ||
for (const [blocClass, callback, scope] of this.blocChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || | ||
(isGlobal && scope === "global") || | ||
(!isGlobal && scope === "local"); | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc, { | ||
nextState: state, | ||
currentState: bloc.state | ||
}); | ||
} | ||
} | ||
} | ||
this.observer.addChange(bloc, state); | ||
for (const [blocClass, callback, scope] of this.blocChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || isGlobal && scope === "global" || !isGlobal && scope === "local"; | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc, { | ||
nextState: state, | ||
currentState: bloc.state | ||
}); | ||
} | ||
notifyValueChange(bloc) { | ||
if (bloc.isClosed) { | ||
return; | ||
} | ||
for (const [blocClass, callback, scope] of this.blocValueChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || | ||
(isGlobal && scope === "global") || | ||
(!isGlobal && scope === "local"); | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc); | ||
} | ||
} | ||
} | ||
} | ||
notifyValueChange(bloc) { | ||
if (bloc.isClosed) { | ||
return; | ||
notifyTransition(bloc, state, event) { | ||
if (bloc.isClosed) { | ||
return; | ||
} | ||
this.observer.addTransition(bloc, state, event); | ||
} | ||
for (const [blocClass, callback, scope] of this.blocValueChangeObservers) { | ||
const isGlobal = this.blocListGlobal.indexOf(bloc) !== -1; | ||
const matchesScope = scope === "all" || isGlobal && scope === "global" || !isGlobal && scope === "local"; | ||
if (matchesScope && bloc instanceof blocClass) { | ||
callback(bloc); | ||
} | ||
addBlocChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
} | ||
notifyTransition(bloc, state, event) { | ||
if (bloc.isClosed) { | ||
return; | ||
addBlocValueChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocValueChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
this.observer.addTransition(bloc, state, event); | ||
} | ||
addBlocChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
addBlocValueChangeObserver(blocClass, callback, scope = "all") { | ||
this.blocValueChangeObservers.push([blocClass, callback, scope]); | ||
} | ||
addLocalBloc(item) { | ||
this.providerList.push(item); | ||
item.bloc.consumer = this; | ||
item.bloc.registerListeners.forEach((fn) => fn(this, item.bloc)); | ||
item.bloc.meta.scope = "local"; | ||
this.observer.addBlocAdded(item.bloc); | ||
} | ||
removeLocalBloc(id, bloc) { | ||
const item = this.providerList.find((i) => i.id === id && i.bloc === bloc); | ||
if (item) { | ||
item.bloc.complete(); | ||
item.bloc.removeListeners.forEach((fn) => fn()); | ||
this.observer.addBlocRemoved(item.bloc); | ||
this.providerList = this.providerList.filter((i) => i !== item); | ||
addLocalBloc(item) { | ||
this.providerList.push(item); | ||
item.bloc.consumer = this; | ||
item.bloc.registerListeners.forEach(fn => fn(this, item.bloc)); | ||
item.bloc.meta.scope = 'local'; | ||
this.observer.addBlocAdded(item.bloc); | ||
} | ||
} | ||
addBlocMock(bloc) { | ||
if (this.mocksEnabled) { | ||
this.mockBlocs = [bloc, ...this.mockBlocs]; | ||
removeLocalBloc(id, bloc) { | ||
const item = this.providerList.find(i => i.id === id && i.bloc === bloc); | ||
if (item) { | ||
item.bloc.complete(); | ||
item.bloc.removeListeners.forEach(fn => fn()); | ||
this.observer.addBlocRemoved(item.bloc); | ||
this.providerList = this.providerList.filter(i => i !== item); | ||
} | ||
} | ||
} | ||
resetMocks() { | ||
this.mockBlocs = []; | ||
} | ||
getGlobalBloc(blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
addBlocMock(bloc) { | ||
if (this.mocksEnabled) { | ||
this.mockBlocs = [bloc, ...this.mockBlocs]; | ||
} | ||
} | ||
return this.blocListGlobal.find((c) => c instanceof blocClass); | ||
} | ||
getLocalBlocForProvider(id, blocClass) { | ||
for (const providerItem of this.providerList) { | ||
if (providerItem.id === id) { | ||
if (providerItem.bloc instanceof blocClass) { | ||
return providerItem.bloc; | ||
resetMocks() { | ||
this.mockBlocs = []; | ||
} | ||
getGlobalBloc(blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
} | ||
let parent = providerItem.parent; | ||
while (parent) { | ||
const parentItem = this.providerList.find((i) => i.id === parent); | ||
if (parentItem?.bloc instanceof blocClass) { | ||
return parentItem.bloc; | ||
} | ||
parent = parentItem?.parent; | ||
return this.blocListGlobal.find(c => c instanceof blocClass); | ||
} | ||
getLocalBlocForProvider(id, blocClass) { | ||
for (const providerItem of this.providerList) { | ||
if (providerItem.id === id) { | ||
if (providerItem.bloc instanceof blocClass) { | ||
return providerItem.bloc; | ||
} | ||
let parent = providerItem.parent; | ||
while (parent) { | ||
const parentItem = this.providerList.find(i => i.id === parent); | ||
if (parentItem?.bloc instanceof blocClass) { | ||
return parentItem.bloc; | ||
} | ||
parent = parentItem?.parent; | ||
} | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
return void 0; | ||
} | ||
getGlobalBlocInstance(global, blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
getGlobalBlocInstance(global, blocClass) { | ||
if (this.mocksEnabled) { | ||
const mockedBloc = this.mockBlocs.find((c) => c instanceof blocClass); | ||
if (mockedBloc) { | ||
return mockedBloc; | ||
} | ||
} | ||
return global.find((c) => c instanceof blocClass); | ||
} | ||
return global.find((c) => c instanceof blocClass); | ||
} | ||
} | ||
const defaultBlocHookOptions = { | ||
subscribe: true | ||
subscribe: true | ||
}; | ||
class BlocRuntimeError { | ||
error; | ||
constructor(message) { | ||
this.error = new Error(message); | ||
} | ||
error; | ||
constructor(message) { | ||
this.error = new Error(message); | ||
} | ||
} | ||
@@ -387,26 +396,26 @@ class NoValue { | ||
class BlocReact extends BlocConsumer { | ||
providerCount = 0; | ||
_blocsGlobal; | ||
_contextLocalProviderKey = React__default["default"].createContext("none"); | ||
constructor(blocs, options) { | ||
super(blocs, options); | ||
this._blocsGlobal = blocs; | ||
this.BlocProvider = this.BlocProvider.bind(this); | ||
this.BlocBuilder = this.BlocBuilder.bind(this); | ||
} | ||
useBloc = (blocClass, options = {}) => { | ||
const mergedOptions = { | ||
...defaultBlocHookOptions, | ||
...options | ||
}; | ||
let blocInstance = React.useMemo(() => options.create ? options.create() : void 0, []); | ||
if (!blocInstance) { | ||
const localProviderKey = React.useContext(this._contextLocalProviderKey); | ||
const localBlocInstance = React.useMemo(() => this.getLocalBlocForProvider(localProviderKey, blocClass), []); | ||
blocInstance = React.useMemo(() => localBlocInstance || this.getGlobalBlocInstance(this._blocsGlobal, blocClass), []); | ||
providerCount = 0; | ||
_blocsGlobal; | ||
_contextLocalProviderKey = React__default["default"].createContext('none'); | ||
constructor(blocs, options) { | ||
super(blocs, options); | ||
this._blocsGlobal = blocs; | ||
this.BlocProvider = this.BlocProvider.bind(this); | ||
this.BlocBuilder = this.BlocBuilder.bind(this); | ||
} | ||
const { subscribe, shouldUpdate = true } = mergedOptions; | ||
if (!blocInstance) { | ||
const name = blocClass.prototype.constructor.name; | ||
const error = new BlocRuntimeError(`"${name}" | ||
useBloc = (blocClass, options = {}) => { | ||
const mergedOptions = { | ||
...defaultBlocHookOptions, | ||
...options | ||
}; | ||
let blocInstance = React.useMemo(() => options.create ? options.create() : undefined, []); | ||
if (!blocInstance) { | ||
const localProviderKey = React.useContext(this._contextLocalProviderKey); | ||
const localBlocInstance = React.useMemo(() => this.getLocalBlocForProvider(localProviderKey, blocClass), []); | ||
blocInstance = React.useMemo(() => localBlocInstance || this.getGlobalBlocInstance(this._blocsGlobal, blocClass), []); | ||
} | ||
const { subscribe, shouldUpdate = true } = mergedOptions; | ||
if (!blocInstance) { | ||
const name = blocClass.prototype.constructor.name; | ||
const error = new BlocRuntimeError(`"${name}" | ||
no bloc with this name was found in the global context. | ||
@@ -426,81 +435,79 @@ | ||
`); | ||
console.error(error.error); | ||
return [ | ||
NoValue, | ||
{}, | ||
{ | ||
error, | ||
complete: true | ||
console.error(error.error); | ||
return [ | ||
NoValue, | ||
{}, | ||
{ | ||
error, | ||
complete: true | ||
} | ||
]; | ||
} | ||
]; | ||
const [data, setData] = React.useState(blocInstance.state); | ||
const updateData = React.useCallback((nextState) => { | ||
if (shouldUpdate === true || shouldUpdate({ nextState, currentState: data })) { | ||
setData(nextState); | ||
} | ||
}, []); | ||
React.useEffect(() => { | ||
if (subscribe) { | ||
const subscription = blocInstance?.subscribe({ | ||
next: updateData | ||
}); | ||
return () => { | ||
subscription?.unsubscribe(); | ||
}; | ||
} | ||
}, []); | ||
return [ | ||
data, | ||
blocInstance | ||
]; | ||
}; | ||
// Components | ||
BlocBuilder(props) { | ||
const hook = this.useBloc(props.blocClass, { | ||
shouldUpdate: props.shouldUpdate | ||
}); | ||
return props.builder(hook); | ||
} | ||
const [data, setData] = React.useState(blocInstance.state); | ||
const updateData = React.useCallback((nextState) => { | ||
if (shouldUpdate === true || shouldUpdate({ nextState, currentState: data })) { | ||
setData(nextState); | ||
} | ||
}, []); | ||
React.useEffect(() => { | ||
if (subscribe) { | ||
const subscription = blocInstance?.subscribe({ | ||
next: updateData | ||
}); | ||
return () => { | ||
subscription?.unsubscribe(); | ||
; | ||
BlocProvider(props) { | ||
const id = React.useMemo(() => createId(), []); | ||
const localProviderKey = React.useContext(this._contextLocalProviderKey); | ||
const bloc = React.useMemo(() => { | ||
const newBloc = typeof props.bloc === "function" ? props.bloc(id) : props.bloc; | ||
if (newBloc) { | ||
this.addLocalBloc({ | ||
bloc: newBloc, | ||
id, | ||
parent: localProviderKey | ||
}); | ||
} | ||
else { | ||
console.error(`BLoC is undefined`); | ||
} | ||
return newBloc; | ||
}, []); | ||
const context = React.useMemo(() => { | ||
return React__default["default"].createContext(bloc); | ||
}, [bloc]); | ||
React.useEffect(() => { | ||
return () => { | ||
this.removeLocalBloc(id, bloc); | ||
}; | ||
}, []); | ||
return (React__default["default"].createElement(this._contextLocalProviderKey.Provider, { value: id }, | ||
React__default["default"].createElement(context.Provider, { value: bloc }, props.children))); | ||
} | ||
; | ||
withBlocProvider = (bloc) => (Component) => { | ||
const { BlocProvider } = this; | ||
return class WithBlocProvider extends React__default["default"].Component { | ||
render() { | ||
return (React__default["default"].createElement(BlocProvider, { bloc: bloc }, | ||
React__default["default"].createElement(Component, { ...this.props }))); | ||
} | ||
}; | ||
} | ||
}, []); | ||
return [ | ||
data, | ||
blocInstance | ||
]; | ||
}; | ||
BlocBuilder(props) { | ||
const hook = this.useBloc(props.blocClass, { | ||
shouldUpdate: props.shouldUpdate | ||
}); | ||
return props.builder(hook); | ||
} | ||
BlocProvider(props) { | ||
const id = React.useMemo(() => createId(), []); | ||
const localProviderKey = React.useContext(this._contextLocalProviderKey); | ||
const bloc = React.useMemo(() => { | ||
const newBloc = typeof props.bloc === "function" ? props.bloc(id) : props.bloc; | ||
if (newBloc) { | ||
this.addLocalBloc({ | ||
bloc: newBloc, | ||
id, | ||
parent: localProviderKey | ||
}); | ||
} else { | ||
console.error(`BLoC is undefined`); | ||
} | ||
return newBloc; | ||
}, []); | ||
const context = React.useMemo(() => { | ||
return React__default["default"].createContext(bloc); | ||
}, [bloc]); | ||
React.useEffect(() => { | ||
return () => { | ||
this.removeLocalBloc(id, bloc); | ||
}; | ||
}, []); | ||
return /* @__PURE__ */ React__default["default"].createElement(this._contextLocalProviderKey.Provider, { | ||
value: id | ||
}, /* @__PURE__ */ React__default["default"].createElement(context.Provider, { | ||
value: bloc | ||
}, props.children)); | ||
} | ||
withBlocProvider = (bloc) => (Component) => { | ||
const { BlocProvider } = this; | ||
return class WithBlocProvider extends React__default["default"].Component { | ||
render() { | ||
return /* @__PURE__ */ React__default["default"].createElement(BlocProvider, { | ||
bloc | ||
}, /* @__PURE__ */ React__default["default"].createElement(Component, { | ||
...this.props | ||
})); | ||
} | ||
}; | ||
}; | ||
} | ||
@@ -507,0 +514,0 @@ |
{ | ||
"name": "bloc-react", | ||
"version": "0.1.27", | ||
"version": "0.1.28", | ||
"license": "MIT", | ||
@@ -9,6 +9,10 @@ "main": "dist/bloc-react.js", | ||
"keywords": [ | ||
"react", | ||
"typescript", | ||
"rxjs", | ||
"state-management", | ||
"reactjs", | ||
"observer-pattern", | ||
"bloc", | ||
"state", | ||
"react", | ||
"reactive" | ||
"bloc-pattern" | ||
], | ||
@@ -26,6 +30,9 @@ "scripts": { | ||
}, | ||
"dependencies": {}, | ||
"dependencies": { | ||
"@rollup/plugin-commonjs": "^21.0.1", | ||
"rollup-plugin-typescript2": "^0.31.1" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.16.7", | ||
"@babel/preset-env": "^7.16.8", | ||
"@babel/core": "^7.16.12", | ||
"@babel/preset-env": "^7.16.11", | ||
"@babel/preset-react": "^7.16.7", | ||
@@ -40,3 +47,3 @@ "@babel/preset-typescript": "^7.16.7", | ||
"@testing-library/react-hooks": "^7.0.2", | ||
"@types/chrome": "^0.0.176", | ||
"@types/chrome": "^0.0.177", | ||
"@types/enzyme": "^3.10.11", | ||
@@ -47,5 +54,5 @@ "@types/jest": "^27.4.0", | ||
"@types/react-dom": "^17.0.11", | ||
"@types/react-router-dom": "^5.3.2", | ||
"@typescript-eslint/eslint-plugin": "^5.10.0", | ||
"@typescript-eslint/parser": "^5.10.0", | ||
"@types/react-router-dom": "^5.3.3", | ||
"@typescript-eslint/eslint-plugin": "^5.10.1", | ||
"@typescript-eslint/parser": "^5.10.1", | ||
"@vitejs/plugin-react-refresh": "^1.3.6", | ||
@@ -55,4 +62,4 @@ "@wojtekmaj/enzyme-adapter-react-17": "^0.6.6", | ||
"enzyme": "^3.11.0", | ||
"esbuild": "^0.14.11", | ||
"eslint": "^8.7.0", | ||
"esbuild": "^0.14.14", | ||
"eslint": "^8.8.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
@@ -73,3 +80,3 @@ "eslint-config-standard": "^16.0.3", | ||
"react-router-dom": "^6.2.1", | ||
"rollup": "^2.64.0", | ||
"rollup": "^2.66.1", | ||
"rollup-plugin-babel": "^4.4.0", | ||
@@ -83,4 +90,4 @@ "rollup-plugin-commonjs": "^10.1.0", | ||
"ts-jest": "^27.1.3", | ||
"typescript": "^4.5.4", | ||
"vite": "^2.7.12" | ||
"typescript": "^4.5.5", | ||
"vite": "^2.7.13" | ||
}, | ||
@@ -87,0 +94,0 @@ "jest": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3944
152655
2
+ Added@rollup/plugin-commonjs@21.1.0(transitive)
+ Added@rollup/pluginutils@3.1.04.2.1(transitive)
+ Added@types/estree@0.0.39(transitive)
+ Added@types/node@22.10.5(transitive)
+ Added@yarn-tool/resolve-package@1.0.47(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedcommondir@1.0.1(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedestree-walker@1.0.12.0.2(transitive)
+ Addedfind-cache-dir@3.3.2(transitive)
+ Addedfind-up@4.1.05.0.0(transitive)
+ Addedfs-extra@10.1.0(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedfsevents@2.3.3(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedis-core-module@2.16.1(transitive)
+ Addedis-reference@1.2.1(transitive)
+ Addedjsonfile@6.1.0(transitive)
+ Addedlocate-path@5.0.06.0.0(transitive)
+ Addedmagic-string@0.25.9(transitive)
+ Addedmake-dir@3.1.0(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedp-limit@2.3.03.1.0(transitive)
+ Addedp-locate@4.1.05.0.0(transitive)
+ Addedp-try@2.2.0(transitive)
+ Addedpath-exists@4.0.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-is-network-drive@1.0.21(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpath-strip-sep@1.0.18(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedpkg-dir@4.2.05.0.0(transitive)
+ Addedresolve@1.22.10(transitive)
+ Addedrollup@2.79.2(transitive)
+ Addedrollup-plugin-typescript2@0.31.2(transitive)
+ Addedsemver@6.3.1(transitive)
+ Addedsourcemap-codec@1.4.8(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedtslib@2.8.1(transitive)
+ Addedtypescript@5.7.3(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addeduniversalify@2.0.1(transitive)
+ Addedupath2@3.1.20(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedyocto-queue@0.1.0(transitive)