Comparing version 5.3.2 to 5.3.3
@@ -68,7 +68,7 @@ # Descriptor | ||
* **arguments**: | ||
* `host` - an element instance | ||
* `lastValue` - last cached value of the property | ||
* **returns (required)**: | ||
* `nextValue` - a value of the current state of the property | ||
- **arguments**: | ||
- `host` - an element instance | ||
- `lastValue` - last cached value of the property | ||
- **returns (required)**: | ||
- `nextValue` - a value of the current state of the property | ||
@@ -83,4 +83,4 @@ `get` calculates the current property value. The returned value is always cached. The cache mechanism works between properties defined by the library (even between different elements). If your `get` method does not use other properties, it won't be called again (then, the only way to update the value is to assert new value or call `invalidate` from `connect` method). | ||
const MyElement = { | ||
firstName: 'John', | ||
lastName: 'Smith', | ||
firstName: "John", | ||
lastName: "Smith", | ||
name: { | ||
@@ -107,8 +107,8 @@ get: ({ firstName, lastName }) => `${firstName} ${lastName}`, | ||
* **arguments**: | ||
* `host` - an element instance | ||
* `value` - a value passed to assertion (ex., `el.myProperty = 'new value'`) | ||
* `lastValue` - last cached value of the property | ||
* **returns (required)**: | ||
* `nextValue` - a value of the property, which replaces cached value | ||
- **arguments**: | ||
- `host` - an element instance | ||
- `value` - a value passed to assertion (ex., `el.myProperty = 'new value'`) | ||
- `lastValue` - last cached value of the property | ||
- **returns (required)**: | ||
- `nextValue` - a value of the property, which replaces cached value | ||
@@ -124,3 +124,3 @@ Every assertion of the property calls `set` method (like `myElement.property = 'new value'`). If returned `nextValue` is not equal to `lastValue`, cache of the property invalidates. However, `set` method does not trigger `get` method automatically. Only the next access to the property (like `const value = myElement.property`) calls `get` method. Then `get` takes `nextValue` from `set` as the `lastValue` argument, calculates `value` and returns it. | ||
}, | ||
} | ||
}; | ||
@@ -149,8 +149,8 @@ myElement.power = 10; // calls 'set' method and set cache to 100 | ||
* **arguments**: | ||
* `host` - an element instance | ||
* `key` - a property key name | ||
* `invalidate` - a callback function, which invalidates cached value | ||
* **returns (not required):** | ||
* `disconnect` - a function (without arguments) | ||
- **arguments**: | ||
- `host` - an element instance | ||
- `key` - a property key name | ||
- `invalidate` - a callback function, which invalidates cached value | ||
- **returns (not required):** | ||
- `disconnect` - a function (without arguments) | ||
@@ -164,3 +164,3 @@ When you insert, remove or relocate an element in the DOM tree, `connect` or `disconnect` is called synchronously (in the `connectedCallback` and `disconnectedCallback` callbacks of the Custom Elements API). | ||
```javascript | ||
import reduxStore from './store'; | ||
import reduxStore from "./store"; | ||
@@ -175,2 +175,8 @@ const MyElement = { | ||
`invalidate` can take an options object argument. | ||
| Option | Type | Default | Description | | ||
| ------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------- | | ||
| force | `boolean` | false | When true, the invalidate call will always trigger a rerender, even if the property's identity has not changed. | | ||
> Click and play with [redux](redux.js.org) library integration example: | ||
@@ -191,6 +197,6 @@ > | ||
* **arguments**: | ||
* `host` - an element instance | ||
* `value` - current value of the property | ||
* `lastValue` - last cached value of the property | ||
- **arguments**: | ||
- `host` - an element instance | ||
- `value` - current value of the property | ||
- `lastValue` - last cached value of the property | ||
@@ -197,0 +203,0 @@ When property cache invalidates (directly by the assertion or when one of the dependency invalidates) and `observe` method is set, the change detection mechanism adds the property to the internal queue. Within the next animation frame (using `requestAnimationFrame`) properties from the queue are checked if they have changed, and if they did, `observe` method of the property is called. It means, that `observe` method is asynchronous by default, and it is only called for properties, which value is different in the time of execution of the queue (in the `requestAnimationFrame` call). |
@@ -5,2 +5,12 @@ # Changelog | ||
### [5.3.3](https://github.com/hybridsjs/hybrids/compare/v5.3.2...v5.3.3) (2021-06-02) | ||
### Bug Fixes | ||
* **cache:** add force option to invalidate callback ([#167](https://github.com/hybridsjs/hybrids/issues/167)) ([91d6ea8](https://github.com/hybridsjs/hybrids/commit/91d6ea82e8963bd4118c3d2b57ad8e84c0cbef64)) | ||
* **cache:** clean contexts on get and set ([26854cb](https://github.com/hybridsjs/hybrids/commit/26854cb3bba2e6fd2c4a654f8c3ed60fe6b10947)) | ||
* **cache:** unresolve deep contexts of suspened target ([b4bb898](https://github.com/hybridsjs/hybrids/commit/b4bb89873bdbf6731358a10f6d6f1d069d7d624b)) | ||
* **store:** computed property saves value in-place ([1a7ab52](https://github.com/hybridsjs/hybrids/commit/1a7ab52395e8a86b60bd0660fec4456534135b6e)) | ||
### [5.3.2](https://github.com/hybridsjs/hybrids/compare/v5.3.1...v5.3.2) (2021-05-28) | ||
@@ -7,0 +17,0 @@ |
{ | ||
"name": "hybrids", | ||
"version": "5.3.2", | ||
"version": "5.3.3", | ||
"description": "The simplest way to create web components with plain objects and pure functions!", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -28,10 +28,2 @@ import * as emitter from "./emitter.js"; | ||
targetMap.set(key, entry); | ||
} else if (entry.contexts.size) { | ||
entry.contexts.forEach(contextEntry => { | ||
if (suspense.has(contextEntry.target)) { | ||
entry.contexts.delete(contextEntry); | ||
contextEntry.depState = 0; | ||
contextEntry.resolved = false; | ||
} | ||
}); | ||
} | ||
@@ -53,2 +45,16 @@ | ||
function cleanContexts(entry) { | ||
entry.contexts.forEach(contextEntry => { | ||
if (suspense.has(contextEntry.target)) { | ||
contextEntry.depState = 0; | ||
contextEntry.resolved = false; | ||
contextEntry.value = undefined; | ||
values.delete(contextEntry); | ||
entry.contexts.delete(contextEntry); | ||
} | ||
}); | ||
} | ||
function dispatchDeep(entry) { | ||
@@ -61,2 +67,3 @@ entry.resolved = false; | ||
cleanContexts(entry); | ||
entry.contexts.forEach(dispatchDeep); | ||
@@ -76,2 +83,4 @@ } | ||
if (!suspense.has(target)) { | ||
cleanContexts(entry); | ||
if (entry.resolved && (!validate || (validate && validate(entry.value)))) { | ||
@@ -103,9 +112,9 @@ return entry.value; | ||
if (contexts.has(entry)) { | ||
throw Error(`Circular get invocation is forbidden: '${key}'`); | ||
} | ||
const lastContext = context; | ||
try { | ||
if (contexts.has(entry)) { | ||
throw Error(`Circular get invocation is forbidden: '${key}'`); | ||
} | ||
entry.deps.forEach(depEntry => { | ||
@@ -126,4 +135,2 @@ depEntry.contexts.delete(entry); | ||
entry.state += 1; | ||
dispatchDeep(entry); | ||
} | ||
@@ -191,17 +198,21 @@ | ||
function invalidateEntry(entry, clearValue, deleteValue) { | ||
function invalidateEntry(entry, options) { | ||
entry.depState = 0; | ||
dispatchDeep(entry); | ||
if (clearValue) { | ||
if (options.clearValue) { | ||
entry.value = undefined; | ||
values.delete(entry); | ||
} | ||
if (deleteValue) { | ||
if (options.deleteEntry) { | ||
deleteEntry(entry); | ||
} | ||
if (options.force) { | ||
entry.state += 1; | ||
} | ||
} | ||
export function invalidate(target, key, clearValue, deleteValue) { | ||
export function invalidate(target, key, options = {}) { | ||
if (contexts.size) { | ||
@@ -214,6 +225,6 @@ throw Error( | ||
const entry = getEntry(target, key); | ||
invalidateEntry(entry, clearValue, deleteValue); | ||
invalidateEntry(entry, options); | ||
} | ||
export function invalidateAll(target, clearValue, deleteValue) { | ||
export function invalidateAll(target, options = {}) { | ||
if (contexts.size) { | ||
@@ -228,3 +239,3 @@ throw Error( | ||
targetMap.forEach(entry => { | ||
invalidateEntry(entry, clearValue, deleteValue); | ||
invalidateEntry(entry, options); | ||
}); | ||
@@ -231,0 +242,0 @@ } |
@@ -75,4 +75,6 @@ import property from "./property.js"; | ||
callbacks.push(host => | ||
config.connect(host, key, () => { | ||
cache.invalidate(host, key); | ||
config.connect(host, key, options => { | ||
cache.invalidate(host, key, { | ||
force: options && options.force === true, | ||
}); | ||
}), | ||
@@ -106,9 +108,7 @@ ); | ||
const type = typeof hybrids[key]; | ||
cache.invalidate( | ||
node, | ||
key, | ||
const clearValue = | ||
type !== "object" && | ||
type !== "function" && | ||
hybrids[key] !== prevHybrids[key], | ||
); | ||
type !== "function" && | ||
hybrids[key] !== prevHybrids[key]; | ||
cache.invalidate(node, key, { clearValue }); | ||
}); | ||
@@ -115,0 +115,0 @@ |
@@ -275,3 +275,3 @@ /* eslint-disable no-use-before-define */ | ||
invalidatePromise = resolvedPromise.then(() => { | ||
cache.invalidate(config, config, true); | ||
cache.invalidate(config, config, { clearValue: true }); | ||
invalidatePromise = null; | ||
@@ -329,5 +329,12 @@ }); | ||
return model => { | ||
let resolved; | ||
let value; | ||
Object.defineProperty(model, key, { | ||
get() { | ||
return cache.get(this, key, defaultValue); | ||
if (!resolved) { | ||
value = defaultValue(this); | ||
resolved = true; | ||
} | ||
return value; | ||
}, | ||
@@ -1038,3 +1045,3 @@ }); | ||
if (config) { | ||
cache.invalidate(config, model.id, clearValue, true); | ||
cache.invalidate(config, model.id, { clearValue, deleteEntry: true }); | ||
} else { | ||
@@ -1046,3 +1053,3 @@ if (!configs.get(model) && !lists.get(model[0])) { | ||
} | ||
cache.invalidateAll(bootstrap(model), clearValue, true); | ||
cache.invalidateAll(bootstrap(model), { clearValue, deleteEntry: true }); | ||
} | ||
@@ -1271,3 +1278,3 @@ } | ||
? (host, key) => () => { | ||
cache.invalidate(host, key, true); | ||
cache.invalidate(host, key, { clearValue: true }); | ||
clear(Model, false); | ||
@@ -1274,0 +1281,0 @@ } |
@@ -6,2 +6,6 @@ // tslint:disable-next-line:export-just-namespace | ||
declare namespace hybrids { | ||
interface InvalidateOptions { | ||
force?: boolean; | ||
} | ||
interface Descriptor<E, V> { | ||
@@ -13,3 +17,3 @@ get?(host: E & HTMLElement, lastValue: V | undefined): V; | ||
key: keyof E, | ||
invalidate: Function, | ||
invalidate: (options?: InvalidateOptions) => void, | ||
): Function | void; | ||
@@ -16,0 +20,0 @@ observe?(host: E & HTMLElement, value: V, lastValue: V): void; |
Sorry, the diff of this file is not supported yet
285210
2766