Skip to content

Commit 925f90d

Browse files
authored
Merge pull request #4 from nicholasrice/users/nicholasrice/implement-derived-function
Add derived value
2 parents dfaeb39 + a51763b commit 925f90d

File tree

6 files changed

+99
-64
lines changed

6 files changed

+99
-64
lines changed

doc-site/docs/getting-started.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ const myLibraryConfig: Library.Config<IMyLibrary> = {
7474

7575
### Tokens
7676

77-
Each token in the library must have a `value` property, and groups must not have a value. Tokens can be assigned three types of values: **static**, **alias**, and **computed**.
77+
Each token in the library must have a `value` property, and groups must not have a value. Tokens can be assigned three types of values: **static**, **alias**, and **derived**.
7878

7979
```ts
8080
interface Colors {
8181
static: DesignToken.Color;
8282
alias: DesignToken.Color;
83-
computed: DesignToken.Color;
83+
derived: DesignToken.Color;
8484
}
8585

8686
const config: Library.Config<Colors> = {
@@ -92,7 +92,7 @@ const config: Library.Config<Colors> = {
9292
type: DesignToken.Type.Color,
9393
value: (tokens) => tokens.static, // alias to the 'static' token
9494
},
95-
computed: {
95+
derived: {
9696
type: DesignToken.Type.Color,
9797
// Operate on the value of the 'alias' token
9898
value: (tokens) => darken(tokens.alias.value, 0.3),
@@ -106,7 +106,7 @@ In alignment with the [DTCG Group](https://design-tokens.github.io/community-gro
106106

107107
## Creating a Library
108108

109-
With the configuration defined, the library can be created. The purpose of the library is to enable changes to token values, notify subscribers to changes, and reconciling alias and computed values with those changes.
109+
With the configuration defined, the library can be created. The purpose of the library is to enable changes to token values, notify subscribers to changes, and reconciling alias and derived values with those changes.
110110

111111
```ts
112112
const library = Library.create(myLibraryConfig);
@@ -144,7 +144,7 @@ library.subscribe(subscriber);
144144
library.tokens.foreground.set("#878787");
145145
```
146146

147-
Change notifications are batched and subscribers get notified each microtask. It's important to note that token values are lazily evaluated. If a computed or alias token has not been accessed, it will **not** notify itself to subscribers even if it's dependencies change:
147+
Change notifications are batched and subscribers get notified each microtask. It's important to note that token values are lazily evaluated. If a derived token has not been accessed, it will **not** notify itself to subscribers even if it's dependencies change:
148148

149149
```ts
150150
const library = Library.create({

src/lib/library.ts

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,53 @@ export namespace Library {
3737
: never;
3838
};
3939

40+
export const DerivedSymbol = Symbol.for("design-token-library::derived");
41+
4042
/**
41-
* A token value that serves as an alias to another token value
43+
* A token value that derives a value from context
4244
*
4345
* @public
4446
*/
45-
export type Alias<T extends DesignToken.Any, R extends Context<any>> = (
46-
context: R
47-
) => T | DesignToken.ValueByToken<T>;
47+
export type DerivedSource<
48+
T extends DesignToken.Any,
49+
R extends Context<any>
50+
> = (context: R) => T | DesignToken.ValueByToken<T>;
51+
export type Derived<
52+
T extends DesignToken.Any,
53+
R extends Context<any>
54+
> = DerivedSource<T, R> & { [DerivedSymbol]: typeof DerivedSymbol };
55+
56+
export function derive<T extends DesignToken.Any, R extends Context<any>>(
57+
value: DerivedSource<T, R>
58+
): Derived<T, R> {
59+
if (isDerived<T, R>(value)) {
60+
return value;
61+
}
62+
63+
Reflect.defineProperty(value, DerivedSymbol, {
64+
value: DerivedSymbol,
65+
});
66+
67+
return value as Derived<T, R>;
68+
}
4869

4970
/**
50-
* An {@link (Library:namespace).Alias} that supports complex token value types
71+
* An {@link (Library:namespace).Derived} that supports complex token value types
5172
* such as {@link DesignToken.Border}
5273
*
5374
* @public
5475
*/
55-
export type DeepAlias<
76+
export type DeepDerived<
5677
V extends DesignToken.Values.Any,
5778
T extends Context<any>
5879
> = {
5980
[K in keyof V]: V[K] extends DesignToken.Values.Any
60-
? V[K] | Alias<DesignToken.TokenByValue<V[K]>, T> | DeepAlias<V[K], T>
81+
? V[K] | Derived<DesignToken.TokenByValue<V[K]>, T> | DeepDerived<V[K], T>
6182
: never;
6283
};
6384

6485
/**
65-
* Context object provided to {@link (Library:namespace).Alias} values at runtime
86+
* Context object provided to {@link (Library:namespace).Derived} values at runtime
6687
*
6788
* @public
6889
*/
@@ -82,7 +103,7 @@ export namespace Library {
82103
* @public
83104
*/
84105
export type Token<T extends DesignToken.Any, C extends {}> = {
85-
set(value: DesignToken.ValueByToken<T> | Alias<T, C>): void;
106+
set(value: DesignToken.ValueByToken<T> | Derived<T, C>): void;
86107
toString(): string;
87108
readonly type: DesignToken.TypeByToken<T>;
88109
readonly extensions: Record<string, any>;
@@ -114,8 +135,8 @@ export namespace Library {
114135
// in Library.create is untyped, it cannot be inferred, so use T | ...
115136
| (Omit<T, "value"> & {
116137
value:
117-
| Library.Alias<T, Context<R>>
118-
| Library.DeepAlias<DesignToken.ValueByToken<T>, Context<R>>;
138+
| Library.Derived<T, Context<R>>
139+
| Library.DeepDerived<DesignToken.ValueByToken<T>, Context<R>>;
119140
});
120141

121142
/**
@@ -147,10 +168,14 @@ const isGroup = (
147168
return isObject(value) && !isToken(value);
148169
};
149170

150-
const isAlias = <T extends DesignToken.Any, K extends {}>(
171+
const isDerived = <T extends DesignToken.Any, K extends {}>(
151172
value: any
152-
): value is Library.Alias<T, K> => {
153-
return typeof value === "function";
173+
): value is Library.Derived<T, K> => {
174+
try {
175+
return Reflect.get(value, Library.DerivedSymbol) === Library.DerivedSymbol;
176+
} catch (e) {
177+
return false;
178+
}
154179
};
155180

156181
const recurseCreate = (
@@ -201,8 +226,8 @@ const recurseCreate = (
201226
);
202227
Reflect.defineProperty(library, key, {
203228
get() {
204-
// Token access needs to be tracked because an alias token
205-
// is a function that returns a token
229+
// Token access needs to be tracked because derived values
230+
// are a function that returns a token
206231
Watcher.track(token);
207232
return token;
208233
},
@@ -282,7 +307,7 @@ const recurseExtend = (
282307
);
283308
Reflect.defineProperty(extendedTokens, key, {
284309
get() {
285-
// Token access needs to be tracked because an alias token
310+
// Token access needs to be tracked because a derived value
286311
// is a function that returns a token
287312
Watcher.track(token);
288313
return token;
@@ -333,7 +358,7 @@ const recurseResolve = (value: any, context: Library.Context<any>) => {
333358
for (const key in value) {
334359
let v = value[key];
335360

336-
if (isAlias(v)) {
361+
if (isDerived(v)) {
337362
v = v(context);
338363
}
339364

@@ -387,16 +412,16 @@ class LibraryImpl<T extends {} = any> implements Library.Library<T> {
387412
class LibraryToken<T extends DesignToken.Any>
388413
implements
389414
Library.Token<any, any>,
390-
ISubscriber<Library.Alias<T, any>>,
415+
ISubscriber<Library.Derived<T, any>>,
391416
IWatcher
392417
{
393-
private raw: DesignToken.ValueByToken<T> | Library.Alias<T, any>;
418+
private raw: DesignToken.ValueByToken<T> | Library.Derived<T, any>;
394419
private cached: DesignToken.ValueByToken<T> | typeof empty = empty;
395420
private subscriptions: Set<INotifier<any>> = new Set();
396421

397422
constructor(
398423
public readonly name: string,
399-
value: DesignToken.ValueByToken<T> | Library.Alias<T, any>,
424+
value: DesignToken.ValueByToken<T> | Library.Derived<T, any>,
400425
private readonly _type: DesignToken.TypeByToken<T>,
401426
private readonly context: Library.Context<any>,
402427
private readonly _description: string,
@@ -429,7 +454,7 @@ class LibraryToken<T extends DesignToken.Any>
429454

430455
this.disconnect();
431456
const stopWatching = Watcher.use(this);
432-
const raw = isAlias(this.raw) ? this.raw(this.context) : this.raw;
457+
const raw = isDerived(this.raw) ? this.raw(this.context) : this.raw;
433458
const normalized = isToken(raw) ? raw.value : raw;
434459

435460
const value = isObject(normalized)
@@ -442,7 +467,7 @@ class LibraryToken<T extends DesignToken.Any>
442467
return value;
443468
}
444469

445-
public set(value: DesignToken.ValueByToken<T> | Library.Alias<T, any>) {
470+
public set(value: DesignToken.ValueByToken<T> | Library.Derived<T, any>) {
446471
this.raw = value;
447472
this.onChange();
448473
}

0 commit comments

Comments
 (0)