Remove dependency to evt in the component library
This commit is contained in:
@ -65,7 +65,6 @@
|
|||||||
"react": "*"
|
"react": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"evt": "^2.5.7",
|
|
||||||
"minimal-polyfills": "^2.2.3",
|
"minimal-polyfills": "^2.2.3",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"tsafe": "^1.6.6"
|
"tsafe": "^1.6.6"
|
||||||
@ -121,6 +120,7 @@
|
|||||||
"vite": "^5.2.11",
|
"vite": "^5.2.11",
|
||||||
"vitest": "^0.29.8",
|
"vitest": "^0.29.8",
|
||||||
"yauzl": "^2.10.0",
|
"yauzl": "^2.10.0",
|
||||||
"zod": "^3.17.10"
|
"zod": "^3.17.10",
|
||||||
|
"evt": "^2.5.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,13 @@ import { fallbackLanguageTag } from "keycloakify/login/i18n/i18n";
|
|||||||
import { useConst } from "keycloakify/tools/useConst";
|
import { useConst } from "keycloakify/tools/useConst";
|
||||||
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Evt } from "evt";
|
import {
|
||||||
import { useRerenderOnStateChange } from "evt/hooks/useRerenderOnStateChange";
|
createStatefulObservable,
|
||||||
|
useRerenderOnChange
|
||||||
|
} from "keycloakify/tools/StatefulObservable";
|
||||||
import { KcContext } from "../kcContext";
|
import { KcContext } from "../kcContext";
|
||||||
|
|
||||||
const evtTermsMarkdown = Evt.create<string | undefined>(undefined);
|
const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);
|
||||||
|
|
||||||
export type KcContextLike = {
|
export type KcContextLike = {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
@ -45,15 +47,15 @@ export function useDownloadTerms(params: {
|
|||||||
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
|
||||||
downloadTermMarkdownMemoized(
|
downloadTermMarkdownMemoized(
|
||||||
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
|
||||||
).then(thermMarkdown => (evtTermsMarkdown.state = thermMarkdown));
|
).then(thermMarkdown => (obsTermsMarkdown.current = thermMarkdown));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTermsMarkdown() {
|
export function useTermsMarkdown() {
|
||||||
useRerenderOnStateChange(evtTermsMarkdown);
|
useRerenderOnChange(obsTermsMarkdown);
|
||||||
|
|
||||||
const termsMarkdown = evtTermsMarkdown.state;
|
const termsMarkdown = obsTermsMarkdown.current;
|
||||||
|
|
||||||
return { termsMarkdown };
|
return { termsMarkdown };
|
||||||
}
|
}
|
||||||
|
16
src/tools/StatefulObservable/README.md
Normal file
16
src/tools/StatefulObservable/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
`StatefulObservable` is a construct that allow to avoid having to depend on [EVT](https://evt.land).
|
||||||
|
|
||||||
|
A `StatefulObservable` can be converted to an evt with:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { statefulObservableToStatefulEvt } from "powerhooks/tools/StatefulObservable/statefulObservableToStatefulEvt";
|
||||||
|
|
||||||
|
const evtXyz = statefulObservableToStatefulEvt({
|
||||||
|
statefulObservable: $xyz
|
||||||
|
//Optionally you can pass a Ctx
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
WARNING: Unlike `StatefulEvt`, `StatefulObservable` do not post when we first attach.
|
||||||
|
If the current value was not yet evaluated `next()` is called on the initial value returned by the function that
|
||||||
|
returns it.
|
58
src/tools/StatefulObservable/StatefulObservable.ts
Normal file
58
src/tools/StatefulObservable/StatefulObservable.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { is } from "tsafe/is";
|
||||||
|
|
||||||
|
export type StatefulObservable<T> = {
|
||||||
|
current: T;
|
||||||
|
subscribe: (next: (data: T) => void) => Subscription;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Subscription = {
|
||||||
|
unsubscribe(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createStatefulObservable<T>(
|
||||||
|
getInitialValue: () => T
|
||||||
|
): StatefulObservable<T> {
|
||||||
|
const nextFunctions: ((data: T) => void)[] = [];
|
||||||
|
|
||||||
|
const { get, set } = (() => {
|
||||||
|
let wrappedState: [T] | undefined = undefined;
|
||||||
|
|
||||||
|
function set(data: T) {
|
||||||
|
wrappedState = [data];
|
||||||
|
|
||||||
|
nextFunctions.forEach(next => next(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
get: () => {
|
||||||
|
if (wrappedState === undefined) {
|
||||||
|
set(getInitialValue());
|
||||||
|
assert(!is<undefined>(wrappedState));
|
||||||
|
}
|
||||||
|
return wrappedState[0];
|
||||||
|
},
|
||||||
|
set
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
return Object.defineProperty(
|
||||||
|
{
|
||||||
|
current: null as any as T,
|
||||||
|
subscribe: (next: (data: T) => void) => {
|
||||||
|
nextFunctions.push(next);
|
||||||
|
|
||||||
|
return {
|
||||||
|
unsubscribe: () =>
|
||||||
|
nextFunctions.splice(nextFunctions.indexOf(next), 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"current",
|
||||||
|
{
|
||||||
|
enumerable: true,
|
||||||
|
get,
|
||||||
|
set
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
2
src/tools/StatefulObservable/hooks/index.ts
Normal file
2
src/tools/StatefulObservable/hooks/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./useObservable";
|
||||||
|
export * from "./useRerenderOnChange";
|
25
src/tools/StatefulObservable/hooks/useObservable.ts
Normal file
25
src/tools/StatefulObservable/hooks/useObservable.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import type { Subscription } from "../StatefulObservable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent of https://docs.evt.land/api/react-hooks
|
||||||
|
*/
|
||||||
|
export function useObservable(
|
||||||
|
effect: (params: {
|
||||||
|
registerSubscription: (subscription: Subscription) => void;
|
||||||
|
}) => void,
|
||||||
|
deps: React.DependencyList
|
||||||
|
): void {
|
||||||
|
useEffect(() => {
|
||||||
|
const subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
effect({
|
||||||
|
registerSubscription: subscription => subscriptions.push(subscription)
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||||
|
subscriptions.length = 0;
|
||||||
|
};
|
||||||
|
}, deps);
|
||||||
|
}
|
19
src/tools/StatefulObservable/hooks/useRerenderOnChange.ts
Normal file
19
src/tools/StatefulObservable/hooks/useRerenderOnChange.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useObservable } from "./useObservable";
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { StatefulObservable } from "../StatefulObservable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent of https://docs.evt.land/api/react-hooks
|
||||||
|
* */
|
||||||
|
export function useRerenderOnChange($: StatefulObservable<unknown>): void {
|
||||||
|
//NOTE: We use function in case the state is a function
|
||||||
|
const [, setCurrent] = useState(() => $.current);
|
||||||
|
|
||||||
|
useObservable(
|
||||||
|
({ registerSubscription }) => {
|
||||||
|
const subscription = $.subscribe(current => setCurrent(() => current));
|
||||||
|
registerSubscription(subscription);
|
||||||
|
},
|
||||||
|
[$]
|
||||||
|
);
|
||||||
|
}
|
2
src/tools/StatefulObservable/index.ts
Normal file
2
src/tools/StatefulObservable/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./StatefulObservable";
|
||||||
|
export * from "./hooks";
|
Reference in New Issue
Block a user