Skip to content

Commit 60a8b34

Browse files
committed
Update types and README
1 parent d387a83 commit 60a8b34

File tree

3 files changed

+143
-19
lines changed

3 files changed

+143
-19
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,3 +1079,46 @@ Below is a table of the places where such line breaks can be used:
10791079
## PDF generation
10801080

10811081
This package requires jspdf as a peer dependency. Please install it in your project if you intend on using the PDF printing feature.
1082+
1083+
## `useObjectBindings`
1084+
1085+
A composable that **flattens** a reactive object into a set of computed refs—one for each “leaf” property—so you can easily bind to deeply nested values by their string paths.
1086+
1087+
### Why use it?
1088+
1089+
- **Automatic reactivity**: Every nested property becomes a `ComputedRef`, with automatic getters/setters that keep your source object in sync.
1090+
- **Flat API surface**: Access or update any nested field by its dot‑delimited path, without writing deep destructuring or `ref` plumbing.
1091+
- **Dynamic path support**: New paths added at runtime are discovered automatically (and proxied), so you never lose reactivity when mutating the structure.
1092+
1093+
```js
1094+
import { useObjectBindings } from "vue-data-ui";
1095+
import type { Ref, ComputedRef } from "vue";
1096+
1097+
const config = ref({
1098+
customPalette: ["#CCCCCC", "#1A1A1A"],
1099+
style: {
1100+
chart: {
1101+
backgroundColor: "#FFFFFF",
1102+
color: "#1A1A1A",
1103+
},
1104+
},
1105+
});
1106+
1107+
const bindings = useObjectBindings(config);
1108+
// Now `bindings` has computed refs for each leaf path:
1109+
// bindings["style.chart.backgroundColor"] → ComputedRef<string>
1110+
// bindings["style.chart.color"] → ComputedRef<string>
1111+
// bindings["customPalette"] → ComputedRef<boolean>
1112+
// // by default, arrays are skipped:
1113+
// // no "customPalette.0", unless you disable skipArrays
1114+
```
1115+
1116+
You can then use these in your template:
1117+
1118+
```html
1119+
<template>
1120+
<div>
1121+
<input type="color" v-model="bindings['style.chart.backgroundColor']" />
1122+
</div>
1123+
</template>
1124+
```

src/useObjectBindings.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,6 @@ function setPropertyByPath(obj, path, value, delimiter) {
8686
current[keys[keys.length - 1]] = value;
8787
}
8888

89-
/**
90-
* Flattens a reactive config object into computed refs for every leaf property.
91-
*
92-
* @template T extends object
93-
* @param {import('vue').Ref<T>} configRef
94-
* @param {{ delimiter?: string; skipArrays?: boolean }} [options]
95-
* @returns {Record<string, import('vue').ComputedRef<unknown>>}
96-
*/
9789
export function useObjectBindings(configRef, options) {
9890
const { delimiter = '.', skipArrays = true } = options || {};
9991
const bindings = {};

types/vue-data-ui.d.ts

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
declare module "vue-data-ui" {
2-
import { DefineComponent } from "vue";
2+
import { Ref, ComputedRef, DefineComponent } from "vue";
33

44
export type VueUiUnknownObj = {
55
[key: string]: unknown;
@@ -5235,7 +5235,7 @@ declare module "vue-data-ui" {
52355235
[key: string]: string | number | number[];
52365236
};
52375237

5238-
export type VueUiQuickChartDataset =
5238+
export type VueUiQuickChartDataset =
52395239
| number[]
52405240
| VueUiQuickChartDatasetObjectItem
52415241
| VueUiQuickChartDatasetObjectItem[];
@@ -7430,9 +7430,9 @@ declare module "vue-data-ui" {
74307430
* - Handles arrays by making their item type DeepPartial
74317431
*/
74327432
export type DeepPartial<T> =
7433-
T extends Function
7434-
? T
7435-
: T extends Array<infer U>
7433+
T extends Function
7434+
? T
7435+
: T extends Array<infer U>
74367436
? Array<DeepPartial<U>>
74377437
: T extends object
74387438
? { [K in keyof T]?: DeepPartial<T[K]> }
@@ -7455,12 +7455,12 @@ declare module "vue-data-ui" {
74557455
* })
74567456
*/
74577457
export function mergeConfigs<T extends Record<string, any>>({
7458-
defaultConfig,
7459-
userConfig,
7460-
}: {
7461-
defaultConfig: T;
7462-
userConfig: DeepPartial<T>;
7463-
}
7458+
defaultConfig,
7459+
userConfig,
7460+
}: {
7461+
defaultConfig: T;
7462+
userConfig: DeepPartial<T>;
7463+
}
74647464
): T;
74657465

74667466
/**
@@ -7648,4 +7648,93 @@ declare module "vue-data-ui" {
76487648
x,
76497649
y
76507650
}: CreateTSpansArgs) => string;
7651+
7652+
export type UseObjectBindingsOptions = {
7653+
/** Delimiter to join object‑path segments */
7654+
delimiter?: string;
7655+
/** If true, array indices will not be traversed */
7656+
skipArrays?: boolean;
7657+
};
7658+
7659+
/**
7660+
* Recursively build a union of dot‑delimited paths for an object type,
7661+
* but skip arrays (we don’t traverse them by default at runtime).
7662+
*/
7663+
type Paths<T> = T extends object
7664+
? T extends any[]
7665+
? never
7666+
: {
7667+
[K in Extract<keyof T, string>]:
7668+
// if the property is itself an object, recurse
7669+
T[K] extends object
7670+
? `${K}` | `${K}.${Paths<T[K]>}`
7671+
: `${K}`;
7672+
}[Extract<keyof T, string>]
7673+
: never;
7674+
7675+
/**
7676+
* Given an object type `T` and one of its path strings `P`,
7677+
* resolve the type at that path.
7678+
*/
7679+
type PathValue<T, P extends string> =
7680+
P extends `${infer K}.${infer Rest}`
7681+
? K extends keyof T
7682+
? PathValue<T[K], Rest>
7683+
: never
7684+
: P extends keyof T
7685+
? T[P]
7686+
: never;
7687+
7688+
/**
7689+
* A fully‑typed bindings record: for each valid path `P` in `T`,
7690+
* `ComputedRef` of the exact `PathValue<T,P>`.
7691+
*/
7692+
export type TypedBindings<T extends object> = {
7693+
[P in Paths<T>]: ComputedRef<PathValue<T, P>>;
7694+
};
7695+
7696+
/**
7697+
* Vue Data UI composable
7698+
* ---
7699+
* Flattens a reactive config object into computed refs for every leaf property.
7700+
*
7701+
* @template T extends object
7702+
* @param configRef A Vue `Ref<T>` holding your object.
7703+
* @param options Optional settings: `delimiter` (default `"."`) and `skipArrays` (default `true`).
7704+
* @returns A `TypedBindings<T>` whose keys are every “leaf” path in `T`
7705+
* and whose values are `ComputedRef` of the exact property type.
7706+
*
7707+
* ___
7708+
* @example
7709+
*
7710+
* ```js
7711+
* import { useObjectBindings } from "vue-data-ui";
7712+
* import type { Ref, ComputedRef } from "vue";
7713+
*
7714+
* const config = ref({
7715+
* customPalette: ["#CCCCCC", "#1A1A1A"],
7716+
* style: {
7717+
* chart: {
7718+
* backgroundColor: "#FFFFFF",
7719+
* color: "#1A1A1A",
7720+
* },
7721+
* },
7722+
* });
7723+
*
7724+
* const bindings = useObjectBindings(config);
7725+
* ```
7726+
*
7727+
* Then in your template:
7728+
* ```html
7729+
* <template>
7730+
* <div>
7731+
* <input type="color" v-model="bindings['style.chart.backgroundColor']" />
7732+
* </div>
7733+
* </template>
7734+
* ```
7735+
*/
7736+
export function useObjectBindings<T extends object>(
7737+
configRef: Ref<T>,
7738+
options?: UseObjectBindingsOptions
7739+
): TypedBindings<T>;
76517740
}

0 commit comments

Comments
 (0)