@dxos/lit-theme-editor 0.8.2-main.10c050d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +3 -0
- package/dist/src/dx-theme-editor/dx-range-spinbutton.d.ts +16 -0
- package/dist/src/dx-theme-editor/dx-range-spinbutton.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/dx-range-spinbutton.js +127 -0
- package/dist/src/dx-theme-editor/dx-range-spinbutton.js.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts +21 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.js +267 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.js.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts +19 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.js +163 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.js.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts +32 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.js +474 -0
- package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.js.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.d.ts +16 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.js +160 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.js.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts +22 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.js +27 -0
- package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.js.map +1 -0
- package/dist/src/dx-theme-editor/index.d.ts +5 -0
- package/dist/src/dx-theme-editor/index.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/index.js +8 -0
- package/dist/src/dx-theme-editor/index.js.map +1 -0
- package/dist/src/dx-theme-editor/util.d.ts +8 -0
- package/dist/src/dx-theme-editor/util.d.ts.map +1 -0
- package/dist/src/dx-theme-editor/util.js +61 -0
- package/dist/src/dx-theme-editor/util.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/src/dx-theme-editor/dx-range-spinbutton.d.ts +16 -0
- package/dist/types/src/dx-theme-editor/dx-range-spinbutton.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-range-spinbutton.js +127 -0
- package/dist/types/src/dx-theme-editor/dx-range-spinbutton.js.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts +21 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.js +267 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.js.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts +19 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.js +163 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.js.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts +32 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.js +474 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.js.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.d.ts +16 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.js +160 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.js.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts +22 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.js +27 -0
- package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.js.map +1 -0
- package/dist/types/src/dx-theme-editor/index.d.ts +5 -0
- package/dist/types/src/dx-theme-editor/index.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/index.js +8 -0
- package/dist/types/src/dx-theme-editor/index.js.map +1 -0
- package/dist/types/src/dx-theme-editor/util.d.ts +8 -0
- package/dist/types/src/dx-theme-editor/util.d.ts.map +1 -0
- package/dist/types/src/dx-theme-editor/util.js +61 -0
- package/dist/types/src/dx-theme-editor/util.js.map +1 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/index.js +5 -0
- package/dist/types/src/index.js.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +40 -0
- package/src/dx-theme-editor/dx-range-spinbutton.ts +124 -0
- package/src/dx-theme-editor/dx-theme-editor-alias-colors.ts +305 -0
- package/src/dx-theme-editor/dx-theme-editor-physical-colors.ts +179 -0
- package/src/dx-theme-editor/dx-theme-editor-semantic-colors.ts +558 -0
- package/src/dx-theme-editor/dx-theme-editor.lit-stories.ts +37 -0
- package/src/dx-theme-editor/dx-theme-editor.pcss +299 -0
- package/src/dx-theme-editor/dx-theme-editor.ts +158 -0
- package/src/dx-theme-editor/index.ts +8 -0
- package/src/dx-theme-editor/util.ts +66 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type AlphaLuminosity } from '@ch-ui/colors';
|
|
6
|
+
import { type TokenSet, parseAlphaLuminosity } from '@ch-ui/tokens';
|
|
7
|
+
import { LitElement, html } from 'lit';
|
|
8
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
9
|
+
import { repeat } from 'lit/directives/repeat.js';
|
|
10
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
11
|
+
|
|
12
|
+
import { debounce } from '@dxos/async';
|
|
13
|
+
import { makeId } from '@dxos/react-hooks';
|
|
14
|
+
|
|
15
|
+
import { restore, saveAndRender, tokenSetUpdateEvent } from './util';
|
|
16
|
+
|
|
17
|
+
import './dx-range-spinbutton';
|
|
18
|
+
|
|
19
|
+
export type DxThemeEditorSemanticColorsProps = {};
|
|
20
|
+
|
|
21
|
+
const isAlphaLuminosity = (value: any): value is AlphaLuminosity => {
|
|
22
|
+
return Number.isFinite(value) || (typeof value === 'string' && value.includes('/'));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
@customElement('dx-theme-editor-semantic-colors')
|
|
26
|
+
export class DxThemeEditorSemanticColors extends LitElement {
|
|
27
|
+
@state()
|
|
28
|
+
tokenSet: TokenSet = restore();
|
|
29
|
+
|
|
30
|
+
@state()
|
|
31
|
+
searchTerm: string = '';
|
|
32
|
+
|
|
33
|
+
private debouncedSaveAndRender = debounce(() => {
|
|
34
|
+
saveAndRender(this.tokenSet);
|
|
35
|
+
}, 200);
|
|
36
|
+
|
|
37
|
+
private handleTokenSetUpdate = () => {
|
|
38
|
+
this.tokenSet = restore();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
private getPhysicalColorSeries(): string[] {
|
|
42
|
+
if (!this.tokenSet.colors?.physical?.series) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return Object.keys(this.tokenSet.colors.physical.series);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private getSemanticTokens(): [string, any][] {
|
|
50
|
+
if (!this.tokenSet.colors?.semantic?.sememes) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Object.entries(this.tokenSet.colors.semantic.sememes);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private updateSemanticToken(tokenName: string, condition: 'light' | 'dark', property: 0 | 1, value: any): void {
|
|
58
|
+
if (!this.tokenSet.colors?.semantic?.sememes?.[tokenName]?.[condition]) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
63
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
64
|
+
|
|
65
|
+
// Update the specific property
|
|
66
|
+
updatedTokenSet.colors.semantic.sememes[tokenName][condition][property] = value;
|
|
67
|
+
|
|
68
|
+
// Update the state
|
|
69
|
+
this.tokenSet = updatedTokenSet;
|
|
70
|
+
|
|
71
|
+
// Save and render changes
|
|
72
|
+
this.debouncedSaveAndRender();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private handleTokenNameChange(tokenName: string, newName: string): void {
|
|
76
|
+
if (!this.tokenSet.colors?.semantic?.sememes?.[tokenName]) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
81
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
82
|
+
|
|
83
|
+
// Get the token value
|
|
84
|
+
const tokenValue = updatedTokenSet.colors.semantic.sememes[tokenName];
|
|
85
|
+
|
|
86
|
+
// Delete the old token
|
|
87
|
+
delete updatedTokenSet.colors.semantic.sememes[tokenName];
|
|
88
|
+
|
|
89
|
+
// Add the token with the new name
|
|
90
|
+
updatedTokenSet.colors.semantic.sememes[newName] = tokenValue;
|
|
91
|
+
|
|
92
|
+
// Update the state
|
|
93
|
+
this.tokenSet = updatedTokenSet;
|
|
94
|
+
|
|
95
|
+
// Save and render changes
|
|
96
|
+
this.debouncedSaveAndRender();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private handleSeriesChange(tokenName: string, condition: 'light' | 'dark', value: string): void {
|
|
100
|
+
this.updateSemanticToken(tokenName, condition, 0, value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private handleBothSeriesChange(tokenName: string, value: string): void {
|
|
104
|
+
// Update both light and dark series values
|
|
105
|
+
this.updateSemanticToken(tokenName, 'light', 0, value);
|
|
106
|
+
this.updateSemanticToken(tokenName, 'dark', 0, value);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private handleLuminosityChange(tokenName: string, condition: 'light' | 'dark', value: number): void {
|
|
110
|
+
// Get the current value to preserve alpha if it exists
|
|
111
|
+
const currentValue = this.tokenSet.colors?.semantic?.sememes?.[tokenName]?.[condition]?.[1];
|
|
112
|
+
if (!isAlphaLuminosity(currentValue)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse the current value to get the alpha component
|
|
117
|
+
const [, alpha] = parseAlphaLuminosity(currentValue);
|
|
118
|
+
|
|
119
|
+
// If alpha is defined and not 1, use the format "luminosity/alpha"
|
|
120
|
+
// Otherwise, just use the luminosity value
|
|
121
|
+
const newValue = alpha !== undefined && alpha !== 1 ? `${value}/${alpha}` : value;
|
|
122
|
+
|
|
123
|
+
this.updateSemanticToken(tokenName, condition, 1, newValue);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private handleAlphaChange(tokenName: string, value: number): void {
|
|
127
|
+
// Update both light and dark conditions
|
|
128
|
+
['light', 'dark'].forEach((condition) => {
|
|
129
|
+
const currentValue = this.tokenSet.colors?.semantic?.sememes?.[tokenName]?.[condition as 'light' | 'dark']?.[1];
|
|
130
|
+
if (!isAlphaLuminosity(currentValue)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Parse the current value to get the luminosity component
|
|
135
|
+
const [luminosity] = parseAlphaLuminosity(currentValue);
|
|
136
|
+
|
|
137
|
+
// If alpha is 1 (default), just use the luminosity value
|
|
138
|
+
// Otherwise, use the format "luminosity/alpha"
|
|
139
|
+
const newValue = value === 1 ? luminosity : `${luminosity}/${value}`;
|
|
140
|
+
|
|
141
|
+
this.updateSemanticToken(tokenName, condition as 'light' | 'dark', 1, newValue);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private addSemanticToken(): void {
|
|
146
|
+
if (!this.tokenSet.colors?.semantic?.sememes) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
151
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
152
|
+
|
|
153
|
+
// Generate a random ID for the token name
|
|
154
|
+
const tokenName = makeId('sememe--');
|
|
155
|
+
|
|
156
|
+
// Create a new token with default values
|
|
157
|
+
updatedTokenSet.colors.semantic.sememes[tokenName] = {
|
|
158
|
+
light: ['neutral', 500],
|
|
159
|
+
dark: ['neutral', 500],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Update the state
|
|
163
|
+
this.tokenSet = updatedTokenSet;
|
|
164
|
+
|
|
165
|
+
// Save and render changes
|
|
166
|
+
this.debouncedSaveAndRender();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private removeSemanticToken(tokenName: string): void {
|
|
170
|
+
if (!this.tokenSet.colors?.semantic?.sememes?.[tokenName]) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
175
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
176
|
+
|
|
177
|
+
// Delete the token
|
|
178
|
+
delete updatedTokenSet.colors.semantic.sememes[tokenName];
|
|
179
|
+
|
|
180
|
+
// Update the state
|
|
181
|
+
this.tokenSet = updatedTokenSet;
|
|
182
|
+
|
|
183
|
+
// Save and render changes
|
|
184
|
+
this.debouncedSaveAndRender();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private getAliasTokensForSemantic(tokenName: string): { condition: string; name: string }[] {
|
|
188
|
+
if (!this.tokenSet.colors?.alias?.aliases?.[tokenName]) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const aliasTokens: { condition: string; name: string }[] = [];
|
|
193
|
+
const aliases = this.tokenSet.colors.alias.aliases[tokenName];
|
|
194
|
+
|
|
195
|
+
// Process each condition (root, attention)
|
|
196
|
+
Object.entries(aliases).forEach(([condition, names]) => {
|
|
197
|
+
names.forEach((name) => {
|
|
198
|
+
aliasTokens.push({ condition, name });
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return aliasTokens;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private addAliasToken(tokenName: string): void {
|
|
206
|
+
if (!this.tokenSet.colors?.alias?.aliases) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
211
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
212
|
+
|
|
213
|
+
// Ensure the semantic token exists in the aliases structure
|
|
214
|
+
if (!updatedTokenSet.colors.alias.aliases[tokenName]) {
|
|
215
|
+
updatedTokenSet.colors.alias.aliases[tokenName] = {};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Ensure the 'root' condition exists
|
|
219
|
+
if (!updatedTokenSet.colors.alias.aliases[tokenName].root) {
|
|
220
|
+
updatedTokenSet.colors.alias.aliases[tokenName].root = [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Generate a random ID for the alias name
|
|
224
|
+
const aliasName = makeId('alias--');
|
|
225
|
+
|
|
226
|
+
// Add the new alias to the 'root' condition
|
|
227
|
+
updatedTokenSet.colors.alias.aliases[tokenName].root.push(aliasName);
|
|
228
|
+
|
|
229
|
+
// Update the state
|
|
230
|
+
this.tokenSet = updatedTokenSet;
|
|
231
|
+
|
|
232
|
+
// Save and render changes
|
|
233
|
+
this.debouncedSaveAndRender();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private removeAliasToken(tokenName: string, condition: string, aliasName: string): void {
|
|
237
|
+
if (!this.tokenSet.colors?.alias?.aliases?.[tokenName]?.[condition]) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
242
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
243
|
+
|
|
244
|
+
// Find the index of the alias in the array
|
|
245
|
+
const aliasIndex = updatedTokenSet.colors.alias.aliases[tokenName][condition].indexOf(aliasName);
|
|
246
|
+
if (aliasIndex === -1) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Remove the alias from the array
|
|
251
|
+
updatedTokenSet.colors.alias.aliases[tokenName][condition].splice(aliasIndex, 1);
|
|
252
|
+
|
|
253
|
+
// If the condition array is empty, remove it
|
|
254
|
+
if (updatedTokenSet.colors.alias.aliases[tokenName][condition].length === 0) {
|
|
255
|
+
delete updatedTokenSet.colors.alias.aliases[tokenName][condition];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If the token has no more conditions, remove it from aliases
|
|
259
|
+
if (Object.keys(updatedTokenSet.colors.alias.aliases[tokenName]).length === 0) {
|
|
260
|
+
delete updatedTokenSet.colors.alias.aliases[tokenName];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Update the state
|
|
264
|
+
this.tokenSet = updatedTokenSet;
|
|
265
|
+
|
|
266
|
+
// Save and render changes
|
|
267
|
+
this.debouncedSaveAndRender();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private updateAliasToken(
|
|
271
|
+
tokenName: string,
|
|
272
|
+
oldCondition: string,
|
|
273
|
+
oldName: string,
|
|
274
|
+
newCondition: string,
|
|
275
|
+
newName: string,
|
|
276
|
+
): void {
|
|
277
|
+
if (!this.tokenSet.colors?.alias?.aliases?.[tokenName]?.[oldCondition]) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
282
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
283
|
+
|
|
284
|
+
// Find the index of the old alias in the array
|
|
285
|
+
const aliasIndex = updatedTokenSet.colors.alias.aliases[tokenName][oldCondition].indexOf(oldName);
|
|
286
|
+
if (aliasIndex === -1) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Remove the old alias
|
|
291
|
+
updatedTokenSet.colors.alias.aliases[tokenName][oldCondition].splice(aliasIndex, 1);
|
|
292
|
+
|
|
293
|
+
// If the old condition array is empty, remove it
|
|
294
|
+
if (updatedTokenSet.colors.alias.aliases[tokenName][oldCondition].length === 0) {
|
|
295
|
+
delete updatedTokenSet.colors.alias.aliases[tokenName][oldCondition];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Ensure the new condition exists
|
|
299
|
+
if (!updatedTokenSet.colors.alias.aliases[tokenName][newCondition]) {
|
|
300
|
+
updatedTokenSet.colors.alias.aliases[tokenName][newCondition] = [];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Add the new alias to the new condition
|
|
304
|
+
updatedTokenSet.colors.alias.aliases[tokenName][newCondition].push(newName);
|
|
305
|
+
|
|
306
|
+
// Update the state
|
|
307
|
+
this.tokenSet = updatedTokenSet;
|
|
308
|
+
|
|
309
|
+
// Save and render changes
|
|
310
|
+
this.debouncedSaveAndRender();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private checkDuplicateAlias(tokenName: string, condition: string, aliasName: string): boolean {
|
|
314
|
+
if (!this.tokenSet.colors?.alias?.aliases) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check if the alias exists in any other token with the same condition
|
|
319
|
+
for (const [currentTokenName, conditions] of Object.entries(this.tokenSet.colors.alias.aliases)) {
|
|
320
|
+
// Skip the current token
|
|
321
|
+
if (currentTokenName === tokenName) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Check if the condition exists and contains the alias name
|
|
326
|
+
if (conditions[condition] && conditions[condition].includes(aliasName)) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private renderTokenControls(tokenName: string, tokenValue: any) {
|
|
335
|
+
const physicalColorSeries = this.getPhysicalColorSeries();
|
|
336
|
+
const lightSeries = tokenValue.light?.[0] || '';
|
|
337
|
+
const lightLuminosityValue = tokenValue.light?.[1] || 0;
|
|
338
|
+
const darkSeries = tokenValue.dark?.[0] || '';
|
|
339
|
+
const darkLuminosityValue = tokenValue.dark?.[1] || 0;
|
|
340
|
+
|
|
341
|
+
// Parse the luminosity values to extract the alpha components
|
|
342
|
+
const [lightLuminosity, lightAlpha] = parseAlphaLuminosity(lightLuminosityValue);
|
|
343
|
+
const [darkLuminosity, darkAlpha] = parseAlphaLuminosity(darkLuminosityValue);
|
|
344
|
+
|
|
345
|
+
// Use the same series for both light and dark
|
|
346
|
+
const currentSeries = lightSeries || darkSeries;
|
|
347
|
+
|
|
348
|
+
// Use the first defined alpha value, or default to 1
|
|
349
|
+
const currentAlpha = lightAlpha !== undefined ? lightAlpha : darkAlpha !== undefined ? darkAlpha : 1;
|
|
350
|
+
|
|
351
|
+
// Create unique IDs for headings to reference in aria-labelledby
|
|
352
|
+
const tokenHeadingId = `${tokenName}-heading`;
|
|
353
|
+
const lightHeadingId = `${tokenName}-light-heading`;
|
|
354
|
+
const darkHeadingId = `${tokenName}-dark-heading`;
|
|
355
|
+
const seriesSelectId = `${tokenName}-series`;
|
|
356
|
+
const contentId = `${tokenName}-content`;
|
|
357
|
+
const aliasListId = `${tokenName}-alias-list`;
|
|
358
|
+
|
|
359
|
+
// Toggle expanded/collapsed state
|
|
360
|
+
const toggleExpanded = (e: Event) => {
|
|
361
|
+
const button = e.currentTarget as HTMLButtonElement;
|
|
362
|
+
const container = button.closest('.collapsible-token') as HTMLElement;
|
|
363
|
+
const isExpanded = container.getAttribute('data-state') === 'expanded';
|
|
364
|
+
container.setAttribute('data-state', isExpanded ? 'collapsed' : 'expanded');
|
|
365
|
+
button.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
return html`
|
|
369
|
+
<div role="group" class="collapsible-token" data-state="collapsed">
|
|
370
|
+
<h3 id="${tokenHeadingId}" class="token-title">
|
|
371
|
+
<button
|
|
372
|
+
class="toggle-button dx-focus-ring dx-button"
|
|
373
|
+
@click=${toggleExpanded}
|
|
374
|
+
aria-expanded="false"
|
|
375
|
+
aria-controls="${contentId}"
|
|
376
|
+
>
|
|
377
|
+
<dx-icon icon="ph--caret-down--regular"></dx-icon>
|
|
378
|
+
<span class="sr-only">Toggle token controls</span>
|
|
379
|
+
</button>
|
|
380
|
+
<span class="static-token-name" @click=${toggleExpanded}>${tokenName}</span>
|
|
381
|
+
<input
|
|
382
|
+
type="text"
|
|
383
|
+
class="token-name-input dx-focus-ring"
|
|
384
|
+
.value=${tokenName}
|
|
385
|
+
@change=${(e: Event) => this.handleTokenNameChange(tokenName, (e.target as HTMLInputElement).value)}
|
|
386
|
+
aria-label="Token name"
|
|
387
|
+
/>
|
|
388
|
+
<button
|
|
389
|
+
class="remove-token-button dx-focus-ring dx-button"
|
|
390
|
+
@click=${() => this.removeSemanticToken(tokenName)}
|
|
391
|
+
aria-label="Remove token"
|
|
392
|
+
>
|
|
393
|
+
<span class="sr-only">Remove token</span>
|
|
394
|
+
<dx-icon icon="ph--minus--regular" />
|
|
395
|
+
</button>
|
|
396
|
+
</h3>
|
|
397
|
+
<div id="${contentId}" class="token-config-content">
|
|
398
|
+
<div class="token-header">
|
|
399
|
+
<div class="token-series-select">
|
|
400
|
+
<label class="control-label" for="${seriesSelectId}">Palette:</label>
|
|
401
|
+
<select
|
|
402
|
+
id="${seriesSelectId}"
|
|
403
|
+
class="series-select dx-focus-ring"
|
|
404
|
+
.value=${currentSeries}
|
|
405
|
+
@change=${(e: Event) => this.handleBothSeriesChange(tokenName, (e.target as HTMLSelectElement).value)}
|
|
406
|
+
aria-labelledby="${tokenHeadingId}"
|
|
407
|
+
>
|
|
408
|
+
${repeat(
|
|
409
|
+
physicalColorSeries,
|
|
410
|
+
(series) => series,
|
|
411
|
+
(series) => html`<option value="${series}" ?selected=${series === currentSeries}>${series}</option>`,
|
|
412
|
+
)}
|
|
413
|
+
</select>
|
|
414
|
+
</div>
|
|
415
|
+
<dx-range-spinbutton
|
|
416
|
+
label="Alpha"
|
|
417
|
+
min="0"
|
|
418
|
+
max="1"
|
|
419
|
+
step="0.01"
|
|
420
|
+
.value=${currentAlpha}
|
|
421
|
+
headingId=${tokenHeadingId}
|
|
422
|
+
@value-changed=${(e: CustomEvent) => this.handleAlphaChange(tokenName, e.detail.value)}
|
|
423
|
+
></dx-range-spinbutton>
|
|
424
|
+
</div>
|
|
425
|
+
<div role="group" class="control-group">
|
|
426
|
+
<div role="none" class="control-group-item">
|
|
427
|
+
<div class="shade-preview dark">
|
|
428
|
+
<div class="shade" style="${styleMap({ backgroundColor: `var(--dx-${tokenName})` })}"></div>
|
|
429
|
+
</div>
|
|
430
|
+
<dx-range-spinbutton
|
|
431
|
+
label="Dark"
|
|
432
|
+
min="0"
|
|
433
|
+
max="1000"
|
|
434
|
+
step="1"
|
|
435
|
+
.value=${darkLuminosity}
|
|
436
|
+
headingId=${darkHeadingId}
|
|
437
|
+
@value-changed=${(e: CustomEvent) => this.handleLuminosityChange(tokenName, 'dark', e.detail.value)}
|
|
438
|
+
variant="reverse-range"
|
|
439
|
+
></dx-range-spinbutton>
|
|
440
|
+
</div>
|
|
441
|
+
<div role="none" class="control-group-item">
|
|
442
|
+
<div class="shade-preview">
|
|
443
|
+
<div class="shade" style="${styleMap({ backgroundColor: `var(--dx-${tokenName})` })}"></div>
|
|
444
|
+
</div>
|
|
445
|
+
<dx-range-spinbutton
|
|
446
|
+
label="Light"
|
|
447
|
+
min="0"
|
|
448
|
+
max="1000"
|
|
449
|
+
step="1"
|
|
450
|
+
.value=${lightLuminosity}
|
|
451
|
+
headingId=${lightHeadingId}
|
|
452
|
+
@value-changed=${(e: CustomEvent) => this.handleLuminosityChange(tokenName, 'light', e.detail.value)}
|
|
453
|
+
variant="reverse-order"
|
|
454
|
+
></dx-range-spinbutton>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<!-- Alias tokens -->
|
|
459
|
+
<div class="semantic-alias-token-section">
|
|
460
|
+
<ul id="${aliasListId}" class="semantic-alias-token-list">
|
|
461
|
+
${repeat(
|
|
462
|
+
this.getAliasTokensForSemantic(tokenName),
|
|
463
|
+
(alias) => `${tokenName}-${alias.condition}-${alias.name}`,
|
|
464
|
+
(alias) => html`
|
|
465
|
+
<li class="alias-token-item">
|
|
466
|
+
<div role="none" class="condition-and-validation">
|
|
467
|
+
<p
|
|
468
|
+
class="alias-validation"
|
|
469
|
+
style=${styleMap({
|
|
470
|
+
display: this.checkDuplicateAlias(tokenName, alias.condition, alias.name) ? 'flex' : 'none',
|
|
471
|
+
})}
|
|
472
|
+
>
|
|
473
|
+
<dx-icon icon="ph--warning--duotone" size="6"></dx-icon>Duplicate
|
|
474
|
+
</p>
|
|
475
|
+
<select
|
|
476
|
+
class="alias-condition-select dx-focus-ring"
|
|
477
|
+
.value=${alias.condition}
|
|
478
|
+
@change=${(e: Event) => {
|
|
479
|
+
const newCondition = (e.target as HTMLSelectElement).value;
|
|
480
|
+
this.updateAliasToken(tokenName, alias.condition, alias.name, newCondition, alias.name);
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<option value="root">root</option>
|
|
484
|
+
<option value="attention">attention</option>
|
|
485
|
+
</select>
|
|
486
|
+
</div>
|
|
487
|
+
<input
|
|
488
|
+
type="text"
|
|
489
|
+
class="alias-name-input dx-focus-ring"
|
|
490
|
+
.value=${alias.name}
|
|
491
|
+
@change=${(e: Event) => {
|
|
492
|
+
const newName = (e.target as HTMLInputElement).value;
|
|
493
|
+
this.updateAliasToken(tokenName, alias.condition, alias.name, alias.condition, newName);
|
|
494
|
+
}}
|
|
495
|
+
/>
|
|
496
|
+
<button
|
|
497
|
+
class="remove-alias-button dx-focus-ring dx-button"
|
|
498
|
+
@click=${() => this.removeAliasToken(tokenName, alias.condition, alias.name)}
|
|
499
|
+
>
|
|
500
|
+
<span class="sr-only">Remove alias token</span>
|
|
501
|
+
<dx-icon icon="ph--minus--regular" />
|
|
502
|
+
</button>
|
|
503
|
+
</li>
|
|
504
|
+
`,
|
|
505
|
+
)}
|
|
506
|
+
</ul>
|
|
507
|
+
<button class="add-alias-button dx-focus-ring dx-button" @click=${() => this.addAliasToken(tokenName)}>
|
|
508
|
+
Add alias
|
|
509
|
+
</button>
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
override connectedCallback(): void {
|
|
517
|
+
super.connectedCallback();
|
|
518
|
+
saveAndRender(this.tokenSet);
|
|
519
|
+
window.addEventListener(tokenSetUpdateEvent, this.handleTokenSetUpdate);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
override disconnectedCallback(): void {
|
|
523
|
+
super.disconnectedCallback();
|
|
524
|
+
window.removeEventListener(tokenSetUpdateEvent, this.handleTokenSetUpdate);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private handleSearchChange(e: Event): void {
|
|
528
|
+
this.searchTerm = (e.target as HTMLInputElement).value;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
override render() {
|
|
532
|
+
const semanticTokens = this.getSemanticTokens();
|
|
533
|
+
const filteredTokens = semanticTokens.filter(([tokenName]) =>
|
|
534
|
+
tokenName.toLowerCase().includes(this.searchTerm.toLowerCase()),
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
return html`
|
|
538
|
+
<input
|
|
539
|
+
type="search"
|
|
540
|
+
class="token-search dx-focus-ring"
|
|
541
|
+
placeholder="Search semantic tokens…"
|
|
542
|
+
.value=${this.searchTerm}
|
|
543
|
+
@input=${this.handleSearchChange}
|
|
544
|
+
aria-label="Search tokens"
|
|
545
|
+
/>
|
|
546
|
+
${repeat(
|
|
547
|
+
filteredTokens,
|
|
548
|
+
([tokenName]) => tokenName,
|
|
549
|
+
([tokenName, tokenValue]) => this.renderTokenControls(tokenName, tokenValue),
|
|
550
|
+
)}
|
|
551
|
+
<button class="add-token-button dx-focus-ring dx-button" @click=${this.addSemanticToken}>Add token</button>
|
|
552
|
+
`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
override createRenderRoot(): this {
|
|
556
|
+
return this;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import './dx-theme-editor.ts';
|
|
6
|
+
import './dx-theme-editor-physical-colors.ts';
|
|
7
|
+
import './dx-theme-editor-semantic-colors.ts';
|
|
8
|
+
import './dx-theme-editor-alias-colors.ts';
|
|
9
|
+
import './dx-theme-editor.pcss';
|
|
10
|
+
import '@dxos/lit-ui';
|
|
11
|
+
import { html } from 'lit';
|
|
12
|
+
|
|
13
|
+
import { type DxThemeEditorProps } from './dx-theme-editor';
|
|
14
|
+
import { type DxThemeEditorAliasColorsProps } from './dx-theme-editor-alias-colors';
|
|
15
|
+
import { type DxThemeEditorPhysicalColorsProps } from './dx-theme-editor-physical-colors';
|
|
16
|
+
import { type DxThemeEditorSemanticColorsProps } from './dx-theme-editor-semantic-colors';
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
title: 'dx-theme-editor',
|
|
20
|
+
parameters: { layout: 'fullscreen' },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const CombinedThemeEditor = (props: DxThemeEditorProps) => {
|
|
24
|
+
return html`<dx-theme-editor></dx-theme-editor>`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const PhysicalColors = (props: DxThemeEditorPhysicalColorsProps) => {
|
|
28
|
+
return html`<dx-theme-editor-physical-colors></dx-theme-editor-physical-colors>`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const SemanticColors = (props: DxThemeEditorSemanticColorsProps) => {
|
|
32
|
+
return html`<dx-theme-editor-semantic-colors></dx-theme-editor-semantic-colors>`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const AliasColors = (props: DxThemeEditorAliasColorsProps) => {
|
|
36
|
+
return html`<dx-theme-editor-alias-colors></dx-theme-editor-alias-colors>`;
|
|
37
|
+
};
|