Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add delete method to observable map #24

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/observable-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const createObservableMap = <T extends { [key: string]: any }>(
get: [],
set: [],
reset: [],
delete: [],
};

const reset = (): void => {
Expand Down Expand Up @@ -40,6 +41,16 @@ export const createObservableMap = <T extends { [key: string]: any }>(
}
};

const deleteProperty = <P extends keyof T>(propName: P & string) => {
const success = states.delete(propName);

if (success) {
handlers.delete.forEach((cb) => cb(propName));
}

return success;
}

const state = (typeof Proxy === 'undefined'
? {}
: new Proxy(defaultState, {
Expand All @@ -62,6 +73,9 @@ export const createObservableMap = <T extends { [key: string]: any }>(
set(propName as any, value);
return true;
},
deleteProperty(_, propName) {
return deleteProperty(propName as any);
}
})) as T;

const on: OnHandler<T> = (eventName, callback) => {
Expand Down Expand Up @@ -95,12 +109,16 @@ export const createObservableMap = <T extends { [key: string]: any }>(
if (subscription.reset) {
on('reset', subscription.reset);
}
if (subscription.delete) {
on('delete', subscription.delete);
}
});

return {
state,
get,
set,
delete: deleteProperty,
on,
onChange,
use,
Expand Down
47 changes: 47 additions & 0 deletions src/subscriptions/stencil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,43 @@ const cleanupElements = debounce((map: Map<string, any[]>) => {

export const stencilSubscription = <T>({ on }: ObservableMap<T>) => {
const elmsToUpdate = new Map<string, any[]>();
const elmsToSubscriptions = new Map<any, string[]>();
const cleanupMap = new Map<any, () => void>();

const reverseCleanup = (elm, propName) => {
if (cleanupMap.has(elm)) {
cleanupMap.get(elm)();
} else {
const previous = elmsToSubscriptions.get(elm) || [];

elmsToSubscriptions.delete(elm);

const clean = debounce(() => {
const current = elmsToSubscriptions.get(elm);

for (const key of previous) {
if (current.includes(key)) continue;

const elements = elmsToUpdate.get(key).filter((el) => el !== elm);

if (elements.length) {
elmsToUpdate.set(key, elements);
} else {
elmsToUpdate.delete(key);
}

console.log(elmsToUpdate);
}

cleanupMap.delete(elm);
}, 0);

cleanupMap.set(elm, clean);
}

appendToMap(elmsToSubscriptions, elm, propName as string);
};


if (typeof getRenderingRef === 'function') {
// If we are not in a stencil project, we do nothing.
Expand All @@ -35,7 +72,9 @@ export const stencilSubscription = <T>({ on }: ObservableMap<T>) => {
const elm = getRenderingRef();
if (elm) {
appendToMap(elmsToUpdate, propName as string, elm);
reverseCleanup(elm, propName);
}

});

on('set', (propName) => {
Expand All @@ -46,6 +85,14 @@ export const stencilSubscription = <T>({ on }: ObservableMap<T>) => {
cleanupElements(elmsToUpdate);
});

on('delete', (propName) => {
const elements = elmsToUpdate.get(propName as string);
if (elements) {
elmsToUpdate.set(propName as string, elements.filter(forceUpdate));
}
cleanupElements(elmsToUpdate);
})

on('reset', () => {
elmsToUpdate.forEach((elms) => elms.forEach(forceUpdate));
cleanupElements(elmsToUpdate);
Expand Down
19 changes: 19 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface Handlers<T> {
get: GetEventHandler<T>[];
reset: ResetEventHandler[];
set: SetEventHandler<T>[];
delete: DeleteEventHandler<T>[];
}

export type SetEventHandler<StoreType> = (
Expand All @@ -11,12 +12,14 @@ export type SetEventHandler<StoreType> = (
oldValue: any
) => void;
export type GetEventHandler<StoreType> = (key: keyof StoreType) => void;
export type DeleteEventHandler<StoreType> = (key: keyof StoreType) => void;
export type ResetEventHandler = () => void;
export type DisposeEventHandler = () => void;

export interface OnHandler<StoreType> {
(eventName: 'set', callback: SetEventHandler<StoreType>): () => void;
(eventName: 'get', callback: GetEventHandler<StoreType>): () => void;
(eventName: 'delete', callback: DeleteEventHandler<StoreType>): () => void;
(eventName: 'dispose', callback: DisposeEventHandler): () => void;
(eventName: 'reset', callback: ResetEventHandler): () => void;
}
Expand All @@ -32,6 +35,7 @@ export interface Subscription<StoreType> {
newValue: StoreType[KeyFromStoreType],
oldValue: StoreType[KeyFromStoreType]
): void;
delete?<KeyFromStoreType extends keyof StoreType>(key: KeyFromStoreType): void;
reset?(): void;
}

Expand All @@ -43,6 +47,10 @@ export interface Setter<T> {
<P extends keyof T>(propName: P & string, value: T[P]): void;
}

export interface DeleteProperty<T> {
<P extends keyof T>(propName: P & string): void;
}

export interface ObservableMap<T> {
/**
* Proxied object that will detect dependencies and call
Expand Down Expand Up @@ -77,6 +85,17 @@ export interface ObservableMap<T> {
*/
set: Setter<T>;

/**
* Only useful if you need to support IE11.
*
* @example
* const { store, ...store } = createStore<Record<string, Item>>({ a: { id: 'a' } });
*
* delete state.a; // If you don't need to support IE11, use this way.
* store.delete('a'); // If you need to support IE11, use this other way.
*/
delete: DeleteProperty<T>;

/**
* Register a event listener, you can listen to `set`, `get` and `reset` events.
*
Expand Down
13 changes: 13 additions & 0 deletions test-app/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export namespace Components {
"storeKey": "hola" | "adios";
"storeValue": string;
}
interface DisplayMapStore {
}
interface DisplayStore {
"storeKey": "hello" | "goodbye";
}
Expand All @@ -24,6 +26,12 @@ declare global {
prototype: HTMLChangeStoreElement;
new (): HTMLChangeStoreElement;
};
interface HTMLDisplayMapStoreElement extends Components.DisplayMapStore, HTMLStencilElement {
}
var HTMLDisplayMapStoreElement: {
prototype: HTMLDisplayMapStoreElement;
new (): HTMLDisplayMapStoreElement;
};
interface HTMLDisplayStoreElement extends Components.DisplayStore, HTMLStencilElement {
}
var HTMLDisplayStoreElement: {
Expand All @@ -38,6 +46,7 @@ declare global {
};
interface HTMLElementTagNameMap {
"change-store": HTMLChangeStoreElement;
"display-map-store": HTMLDisplayMapStoreElement;
"display-store": HTMLDisplayStoreElement;
"simple-store": HTMLSimpleStoreElement;
}
Expand All @@ -47,13 +56,16 @@ declare namespace LocalJSX {
"storeKey"?: "hola" | "adios";
"storeValue"?: string;
}
interface DisplayMapStore {
}
interface DisplayStore {
"storeKey"?: "hello" | "goodbye";
}
interface SimpleStore {
}
interface IntrinsicElements {
"change-store": ChangeStore;
"display-map-store": DisplayMapStore;
"display-store": DisplayStore;
"simple-store": SimpleStore;
}
Expand All @@ -63,6 +75,7 @@ declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"change-store": LocalJSX.ChangeStore & JSXBase.HTMLAttributes<HTMLChangeStoreElement>;
"display-map-store": LocalJSX.DisplayMapStore & JSXBase.HTMLAttributes<HTMLDisplayMapStoreElement>;
"display-store": LocalJSX.DisplayStore & JSXBase.HTMLAttributes<HTMLDisplayStoreElement>;
"simple-store": LocalJSX.SimpleStore & JSXBase.HTMLAttributes<HTMLSimpleStoreElement>;
}
Expand Down
67 changes: 67 additions & 0 deletions test-app/src/components/display-map-store/display-map-store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Component, Host, h } from '@stencil/core';
import { state, Item } from '../../utils/map-store';

@Component({
tag: 'display-map-store',
shadow: false,
})
export class DisplayMapStore {
private currentId: number = 0;
private deletionIndex: number = 0;

render() {
return (
<Host>
<div>
<button onClick={this.addToMap}>add</button>
<button onClick={this.deleteFromMap}>remove</button>
</div>

<div>
{Array.from({ length: this.currentId + 10 - this.deletionIndex }, (_, i) => {
const item = state[`id-${i + this.deletionIndex}`];

if (!item) return null;

return (
<div
style={{
border: '1px solid grey',
padding: '5px',
margin: '5px',
}}
>
<div>{item.name}</div>
<div>{item.created.toLocaleDateString()}</div>
</div>
);
})}
</div>
</Host>
);
}

private addToMap = () => {
const item: Item = {
id: `id-${this.currentId}`,
name: `item-${this.currentId}`,
created: new Date(),
};

state[item.id] = item;

this.currentId++;
};

private deleteFromMap = () => {
const id = Object.keys(state)[0];

const toDelete = state[id];
console.log('will delete', toDelete);

if (id) {
delete state[id];
this.deletionIndex++;
}
};
}
1 change: 1 addition & 0 deletions test-app/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@

<display-store store-key="hello"></display-store>
<change-store store-key="hello" store-value="other-value"></change-store>
<display-map-store></display-map-store>
</body>
</html>
13 changes: 13 additions & 0 deletions test-app/src/utils/map-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createStore } from '@stencil/store';

export interface Item {
id: string;
name: string;
created: Date;
}

const store = createStore<Record<string, Item>>({});

export const dispose = store.dispose;
export const state = store.state;
export const reset = store.reset;