Skip to content

Commit 9247557

Browse files
committed
0.0.20
1 parent 8bcb9dd commit 9247557

File tree

10 files changed

+123
-36
lines changed

10 files changed

+123
-36
lines changed

chartlets.js/CHANGES.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## Version 0.0.20 (from 2024/11/19)
2+
3+
* Using `FrameworkOptions.getDerivedHostState` also in
4+
`handleHostStoreChange()`.
5+
6+
* Actions `handleComponentChange()` and `handleHostStoreChange()`
7+
now exit immediately, if no extensions are configured yet.
8+
9+
* Module `utils.objPath`: Renamed `toObjPath` into `normalizeObjPath`,
10+
added `formatObjPath`.
11+
12+
## Version 0.0.19 (from 2024/11/18)
13+
14+
* Fixed TypeScript typing issues with `configureFramework<S>()` and
15+
`FrameworkOptions`.
16+
17+
## Version 0.0.18 (from 2024/11/18)
18+
19+
* Fixed TypeScript typing issues with `configureFramework<S>()` and
20+
`FrameworkOptions`.
21+
22+
## Version 0.0.17 (from 2024/11/18)
23+
24+
* Enhanced interface `FrameworkOptions` by property `getDerivedHostState`,
25+
which is a user-supplied function that can compute derived
26+
host state property.
27+
28+
## Version 0.0.16 (from 2024/11/12)
29+
30+
Initial, still experimental version.

chartlets.js/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chartlets.js/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chartlets",
3-
"version": "0.0.18",
3+
"version": "0.0.20",
44
"description": "An experimental library for integrating interactive charts into existing JavaScript applications.",
55
"type": "module",
66
"files": [
@@ -18,10 +18,10 @@
1818
],
1919
"repository": {
2020
"type": "git",
21-
"url": "https://github.com/bcdev/charlet.git"
21+
"url": "https://github.com/bcdev/chartlets.git"
2222
},
2323
"bugs": {
24-
"url": "https://github.com/bcdev/charlet/issues"
24+
"url": "https://github.com/bcdev/chartlets/issues"
2525
},
2626
"homepage": "https://github.com/bcdev/charlet/blob/main/chartlets/README.md",
2727
"author": "Brockmann Consult GmbH",

chartlets.js/src/lib/actions/handleComponentChange.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export function handleComponentChange(
1212
contribIndex: number,
1313
changeEvent: ComponentChangeEvent,
1414
) {
15+
if (store.getState().extensions.length === 0) {
16+
// Exit immediately if there are no extensions (yet)
17+
return;
18+
}
1519
// Apply actual component state change immediately
1620
applyStateChangeRequests([
1721
{

chartlets.js/src/lib/actions/handleHostStoreChange.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ import type {
77
} from "@/lib/types/model/callback";
88
import type { Input } from "@/lib/types/model/channel";
99
import { getInputValues } from "@/lib/actions/helpers/getInputValues";
10-
import { getValue, type ObjPath, toObjPath } from "@/lib/utils/objPath";
10+
import {
11+
getValue,
12+
type ObjPath,
13+
normalizeObjPath,
14+
formatObjPath,
15+
} from "@/lib/utils/objPath";
1116
import { invokeCallbacks } from "@/lib/actions/helpers/invokeCallbacks";
1217
import type { ContributionState } from "@/lib/types/state/contribution";
18+
import type { GetDerivedState } from "@/lib/types/state/store";
1319

1420
/**
1521
* A reference to a property of an input of a callback of a contribution.
@@ -23,11 +29,17 @@ export function handleHostStoreChange<S extends object = object>(
2329
currState: S,
2430
prevState: S,
2531
) {
26-
const { contributionsRecord } = store.getState();
32+
if (store.getState().extensions.length === 0) {
33+
// Exit immediately if there are no extensions (yet)
34+
return;
35+
}
36+
const { configuration, contributionsRecord } = store.getState();
37+
const { getDerivedHostState } = configuration;
2738
const callbackRequests = getCallbackRequests(
2839
contributionsRecord,
2940
currState,
3041
prevState,
42+
getDerivedHostState,
3143
);
3244
invokeCallbacks(callbackRequests);
3345
}
@@ -36,10 +48,16 @@ function getCallbackRequests<S extends object = object>(
3648
contributionsRecord: Record<string, ContributionState[]>,
3749
hostState: S,
3850
prevHostState: S,
51+
getDerivedHostState: GetDerivedState<S> | undefined,
3952
): CallbackRequest[] {
4053
return getHostStorePropertyRefs()
4154
.filter((propertyRef) =>
42-
hasPropertyChanged(propertyRef.propertyPath, hostState, prevHostState),
55+
hasPropertyChanged(
56+
propertyRef.propertyPath,
57+
hostState,
58+
prevHostState,
59+
getDerivedHostState,
60+
),
4361
)
4462
.map((propertyRef) => {
4563
const contributions = contributionsRecord[propertyRef.contribPoint];
@@ -49,6 +67,7 @@ function getCallbackRequests<S extends object = object>(
4967
callback.inputs!,
5068
contribution,
5169
hostState,
70+
getDerivedHostState as GetDerivedState,
5271
);
5372
return { ...propertyRef, inputValues };
5473
});
@@ -74,7 +93,7 @@ function getHostStorePropertyRefs(): PropertyRef[] {
7493
contribIndex,
7594
callbackIndex,
7695
inputIndex,
77-
propertyPath: toObjPath(input.property!),
96+
propertyPath: normalizeObjPath(input.property!),
7897
});
7998
}
8099
}),
@@ -89,8 +108,20 @@ function hasPropertyChanged<S extends object = object>(
89108
propertyPath: ObjPath,
90109
currState: S,
91110
prevState: S,
111+
getDerivedHostState: GetDerivedState<S> | undefined,
92112
): boolean {
93113
const currValue = getValue(currState, propertyPath);
94114
const prevValue = getValue(prevState, propertyPath);
115+
if (
116+
currValue === undefined &&
117+
prevValue === undefined &&
118+
getDerivedHostState !== undefined
119+
) {
120+
const propertyName = formatObjPath(propertyPath);
121+
return !Object.is(
122+
getDerivedHostState(currState, propertyName),
123+
getDerivedHostState(prevState, propertyName),
124+
);
125+
}
95126
return !Object.is(currValue, prevValue);
96127
}

chartlets.js/src/lib/actions/helpers/getInputValues.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import type { Input } from "@/lib/types/model/channel";
22
import type { ContributionState } from "@/lib/types/state/contribution";
33
import type { ComponentState } from "@/lib/types/state/component";
44
import { isContainerState } from "@/lib/actions/helpers/isContainerState";
5-
import { getValue, toObjPath } from '@/lib/utils/objPath';
5+
import { formatObjPath, getValue, normalizeObjPath } from "@/lib/utils/objPath";
66
import { isObject } from "@/lib/utils/isObject";
7+
import type { GetDerivedState } from "@/lib/types/state/store";
78

89
export function getInputValues<S extends object = object>(
910
inputs: Input[],
1011
contributionState: ContributionState,
1112
hostState?: S | undefined,
12-
getDerivedHostState?: (hostState: object, property: string) => unknown,
13+
getDerivedHostState?: GetDerivedState,
1314
): unknown[] {
1415
return inputs.map((input) =>
1516
getInputValue(input, contributionState, hostState, getDerivedHostState),
@@ -22,7 +23,7 @@ export function getInputValue<S extends object = object>(
2223
input: Input,
2324
contributionState: ContributionState,
2425
hostState?: S,
25-
getDerivedHostState?: (hostState: object, propertyName: string) => unknown,
26+
getDerivedHostState?: GetDerivedState<S>,
2627
): unknown {
2728
let inputValue: unknown = undefined;
2829
const dataSource = input.link || "component";
@@ -31,7 +32,11 @@ export function getInputValue<S extends object = object>(
3132
} else if (dataSource === "container" && contributionState.container) {
3233
inputValue = getInputValueFromState(input, contributionState.container);
3334
} else if (dataSource === "app" && hostState) {
34-
inputValue = getInputValueFromState(input, hostState, getDerivedHostState);
35+
inputValue = getInputValueFromState(
36+
input,
37+
hostState,
38+
getDerivedHostState as GetDerivedState,
39+
);
3540
} else {
3641
console.warn(`input with unknown data source:`, input);
3742
}
@@ -66,19 +71,18 @@ export function getInputValueFromComponent(
6671
export function getInputValueFromState(
6772
input: Input,
6873
state: object | undefined,
69-
getDerivedState?: (state: object, propertyName: string) => unknown,
74+
getDerivedState?: GetDerivedState,
7075
): unknown {
7176
let inputValue: unknown = state;
7277
if (input.id && isObject(inputValue)) {
7378
inputValue = inputValue[input.id];
7479
}
7580
if (isObject(inputValue)) {
7681
const state = inputValue;
77-
const property = toObjPath(input.property);
82+
const property = normalizeObjPath(input.property);
7883
inputValue = getValue(state, property);
7984
if (inputValue === undefined && getDerivedState !== undefined) {
80-
const propertyName = property.map(v => typeof v === "number" ? v.toString() : v).join(".");
81-
inputValue = getDerivedState(state, propertyName);
85+
inputValue = getDerivedState(state, formatObjPath(input.property));
8286
}
8387
}
8488
return inputValue;

chartlets.js/src/lib/api/fetchContributions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type Contributions } from "@/lib/types/model/extension";
22
import { type Callback } from "@/lib/types/model/callback";
3-
import { toObjPath } from "@/lib/utils/objPath";
3+
import { normalizeObjPath } from "@/lib/utils/objPath";
44
import { mapObject } from "@/lib/utils/mapObject";
55
import type { Contribution } from "@/lib/types/model/contribution";
66
import type { Channel } from "@/lib/types/model/channel";
@@ -61,6 +61,6 @@ function normalizeChannels<S extends Channel>(channels: S[]): S[] {
6161
function normalizeChannel<S extends Channel>(channel: S): S {
6262
return {
6363
...channel,
64-
property: toObjPath(channel.property),
64+
property: normalizeObjPath(channel.property),
6565
};
6666
}

chartlets.js/src/lib/types/state/store.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import type { ContributionState } from "@/lib/types/state/contribution";
99
import type { ApiOptions, ApiResult } from "@/lib/types/api";
1010
import type { LoggingOptions } from "@/lib/actions/helpers/configureLogging";
1111

12+
export type GetDerivedState<S extends object = object> = (
13+
hostState: S,
14+
propertyName: string,
15+
) => unknown;
16+
1217
export interface FrameworkOptions<S extends object = object> {
1318
/** The host applications state management store. */
1419
hostStore?: StoreApi<S>;
1520
/** Get a derived state from given host state. */
16-
getDerivedHostState?: (hostState: S, propertyName: string) => unknown;
21+
getDerivedHostState?: GetDerivedState<S>;
1722
/** API options to configure backend. */
1823
api?: ApiOptions;
1924
/** Logging options. */

chartlets.js/src/lib/utils/objPath.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect } from "vitest";
22

3-
import { equalObjPaths, getValue, setValue, toObjPath } from "./objPath";
3+
import { equalObjPaths, getValue, setValue, normalizeObjPath } from "./objPath";
44

55
describe("Test that getValue()", () => {
66
it("works on 0th level", () => {
@@ -72,30 +72,30 @@ describe("Test that setValue()", () => {
7272
});
7373
});
7474

75-
describe("Test that toObjPath()", () => {
75+
describe("Test that normalizeObjPath()", () => {
7676
it("does not convert arrays", () => {
7777
const value = [1, 2, 3];
78-
expect(toObjPath(value)).toBe(value);
79-
expect(toObjPath(value)).toEqual([1, 2, 3]);
78+
expect(normalizeObjPath(value)).toBe(value);
79+
expect(normalizeObjPath(value)).toEqual([1, 2, 3]);
8080
});
8181

8282
it("converts undefined", () => {
83-
expect(toObjPath(undefined)).toEqual([]);
83+
expect(normalizeObjPath(undefined)).toEqual([]);
8484
});
8585

8686
it("converts null", () => {
87-
expect(toObjPath(null)).toEqual([]);
87+
expect(normalizeObjPath(null)).toEqual([]);
8888
});
8989

9090
it("converts numbers", () => {
91-
expect(toObjPath(3)).toEqual([3]);
91+
expect(normalizeObjPath(3)).toEqual([3]);
9292
});
9393

9494
it("converts strings", () => {
95-
expect(toObjPath("")).toEqual([]);
96-
expect(toObjPath("colors")).toEqual(["colors"]);
97-
expect(toObjPath("colors.6")).toEqual(["colors", 6]);
98-
expect(toObjPath("colors.6.red")).toEqual(["colors", 6, "red"]);
95+
expect(normalizeObjPath("")).toEqual([]);
96+
expect(normalizeObjPath("colors")).toEqual(["colors"]);
97+
expect(normalizeObjPath("colors.6")).toEqual(["colors", 6]);
98+
expect(normalizeObjPath("colors.6.red")).toEqual(["colors", 6, "red"]);
9999
});
100100
});
101101

chartlets.js/src/lib/utils/objPath.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type ObjPathLike = ObjPath | string | number | undefined | null;
66
type Obj = Record<string | number, unknown>;
77

88
export function getValue(obj: object | undefined, path: ObjPathLike): unknown {
9-
path = toObjPath(path);
9+
path = normalizeObjPath(path);
1010
let value: unknown = obj;
1111
for (const key of path) {
1212
if (isObject(value)) {
@@ -23,7 +23,7 @@ export function setValue<S extends object | undefined>(
2323
path: ObjPathLike,
2424
value: unknown,
2525
): S {
26-
return _setValue(obj, toObjPath(path), value);
26+
return _setValue(obj, normalizeObjPath(path), value);
2727
}
2828

2929
function _setValue<S extends object | undefined>(
@@ -63,7 +63,7 @@ function _setValue<S extends object | undefined>(
6363
return obj;
6464
}
6565

66-
export function toObjPath(pathLike: ObjPathLike): ObjPath {
66+
export function normalizeObjPath(pathLike: ObjPathLike): ObjPath {
6767
if (Array.isArray(pathLike)) {
6868
return pathLike as ObjPath;
6969
} else if (!pathLike || pathLike === "") {
@@ -82,12 +82,25 @@ export function toObjPath(pathLike: ObjPathLike): ObjPath {
8282
}
8383
}
8484

85+
export function formatObjPath(objPath: ObjPathLike): string {
86+
if (typeof objPath === "string") {
87+
return objPath;
88+
} else if (Array.isArray(objPath)) {
89+
return objPath
90+
.map((key) => (typeof key === "number" ? key.toString() : key))
91+
.join(".");
92+
} else if (typeof objPath === "number") {
93+
return objPath.toString();
94+
}
95+
return "";
96+
}
97+
8598
export function equalObjPaths(pathLike1: ObjPathLike, pathLike2: ObjPathLike) {
8699
if (pathLike1 === pathLike2) {
87100
return true;
88101
}
89-
const path1 = toObjPath(pathLike1);
90-
const path2 = toObjPath(pathLike2);
102+
const path1 = normalizeObjPath(pathLike1);
103+
const path2 = normalizeObjPath(pathLike2);
91104
return (
92105
path1.length === path2.length &&
93106
path1.every((item, index) => item === path2[index])

0 commit comments

Comments
 (0)