@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,305 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type TokenSet, type AliasLayer } from '@ch-ui/tokens';
|
|
6
|
+
import { LitElement, html } from 'lit';
|
|
7
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
8
|
+
import { repeat } from 'lit/directives/repeat.js';
|
|
9
|
+
|
|
10
|
+
import { debounce } from '@dxos/async';
|
|
11
|
+
import { makeId } from '@dxos/react-hooks';
|
|
12
|
+
|
|
13
|
+
import { restore, saveAndRender, tokenSetUpdateEvent } from './util';
|
|
14
|
+
|
|
15
|
+
export type DxThemeEditorAliasColorsProps = {};
|
|
16
|
+
|
|
17
|
+
@customElement('dx-theme-editor-alias-colors')
|
|
18
|
+
export class DxThemeEditorAliasColors extends LitElement {
|
|
19
|
+
@state()
|
|
20
|
+
tokenSet: TokenSet = restore();
|
|
21
|
+
|
|
22
|
+
@state()
|
|
23
|
+
searchTerm: string = '';
|
|
24
|
+
|
|
25
|
+
private debouncedSaveAndRender = debounce(() => {
|
|
26
|
+
saveAndRender(this.tokenSet);
|
|
27
|
+
}, 200);
|
|
28
|
+
|
|
29
|
+
private handleTokenSetUpdate = () => {
|
|
30
|
+
this.tokenSet = restore();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
private getSemanticTokenNames(): string[] {
|
|
34
|
+
if (!this.tokenSet.colors?.semantic?.sememes) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return Object.keys(this.tokenSet.colors.semantic.sememes);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private getUniqueAliasTokens(): { name: string; root?: string; attention?: string }[] {
|
|
42
|
+
if (!this.tokenSet.colors?.alias?.aliases) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const aliasMap = new Map<string, { name: string; root?: string; attention?: string }>();
|
|
47
|
+
|
|
48
|
+
// Process all semantic tokens
|
|
49
|
+
Object.entries(this.tokenSet.colors.alias.aliases).forEach(([semanticToken, conditions]) => {
|
|
50
|
+
// Process each condition (root, attention)
|
|
51
|
+
Object.entries(conditions).forEach(([condition, aliasNames]) => {
|
|
52
|
+
// Process each alias name
|
|
53
|
+
aliasNames.forEach((aliasName) => {
|
|
54
|
+
// If the alias is not in the map yet, add it
|
|
55
|
+
if (!aliasMap.has(aliasName)) {
|
|
56
|
+
aliasMap.set(aliasName, { name: aliasName });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Update the condition mapping
|
|
60
|
+
const aliasInfo = aliasMap.get(aliasName)!;
|
|
61
|
+
if (condition === 'root') {
|
|
62
|
+
aliasInfo.root = semanticToken;
|
|
63
|
+
} else if (condition === 'attention') {
|
|
64
|
+
aliasInfo.attention = semanticToken;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return Array.from(aliasMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private updateAliasToken(oldName: string, newName: string, rootToken?: string, attentionToken?: string): void {
|
|
74
|
+
if (!this.tokenSet.colors?.alias?.aliases) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
79
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
80
|
+
const aliases = updatedTokenSet.colors.alias.aliases as AliasLayer['aliases'];
|
|
81
|
+
|
|
82
|
+
// Remove the old alias from all semantic tokens
|
|
83
|
+
Object.entries(aliases).forEach(([semanticToken, conditions]) => {
|
|
84
|
+
Object.entries(conditions).forEach(([condition, aliasNames]) => {
|
|
85
|
+
const index = aliasNames.indexOf(oldName);
|
|
86
|
+
if (index !== -1) {
|
|
87
|
+
aliasNames.splice(index, 1);
|
|
88
|
+
if (aliasNames.length === 0) {
|
|
89
|
+
delete conditions[condition];
|
|
90
|
+
}
|
|
91
|
+
if (Object.keys(conditions).length === 0) {
|
|
92
|
+
delete aliases[semanticToken];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Add the new alias to the specified semantic tokens
|
|
99
|
+
if (rootToken) {
|
|
100
|
+
if (!aliases[rootToken]) {
|
|
101
|
+
aliases[rootToken] = {};
|
|
102
|
+
}
|
|
103
|
+
if (!aliases[rootToken].root) {
|
|
104
|
+
aliases[rootToken].root = [];
|
|
105
|
+
}
|
|
106
|
+
aliases[rootToken].root.push(newName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (attentionToken) {
|
|
110
|
+
if (!aliases[attentionToken]) {
|
|
111
|
+
aliases[attentionToken] = {};
|
|
112
|
+
}
|
|
113
|
+
if (!aliases[attentionToken].attention) {
|
|
114
|
+
aliases[attentionToken].attention = [];
|
|
115
|
+
}
|
|
116
|
+
aliases[attentionToken].attention.push(newName);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update the state
|
|
120
|
+
this.tokenSet = updatedTokenSet;
|
|
121
|
+
|
|
122
|
+
// Save and render changes
|
|
123
|
+
this.debouncedSaveAndRender();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private removeAliasToken(aliasName: string): void {
|
|
127
|
+
if (!this.tokenSet.colors?.alias?.aliases) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
132
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
133
|
+
const aliases = updatedTokenSet.colors.alias.aliases as AliasLayer['aliases'];
|
|
134
|
+
|
|
135
|
+
// Remove the alias from all semantic tokens
|
|
136
|
+
Object.entries(aliases).forEach(([semanticToken, conditions]) => {
|
|
137
|
+
Object.entries(conditions).forEach(([condition, aliasNames]) => {
|
|
138
|
+
const index = aliasNames.indexOf(aliasName);
|
|
139
|
+
if (index !== -1) {
|
|
140
|
+
aliasNames.splice(index, 1);
|
|
141
|
+
if (aliasNames.length === 0) {
|
|
142
|
+
delete conditions[condition];
|
|
143
|
+
}
|
|
144
|
+
if (Object.keys(conditions).length === 0) {
|
|
145
|
+
delete aliases[semanticToken];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Update the state
|
|
152
|
+
this.tokenSet = updatedTokenSet;
|
|
153
|
+
|
|
154
|
+
// Save and render changes
|
|
155
|
+
this.debouncedSaveAndRender();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private addAliasToken(): void {
|
|
159
|
+
if (!this.tokenSet.colors?.semantic?.sememes || !this.tokenSet.colors?.alias?.aliases) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Get the first semantic token
|
|
164
|
+
const semanticTokens = Object.keys(this.tokenSet.colors.semantic.sememes);
|
|
165
|
+
if (semanticTokens.length === 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const firstSemanticToken = semanticTokens[0];
|
|
170
|
+
|
|
171
|
+
// Generate a random ID for the alias name
|
|
172
|
+
const aliasName = makeId('alias--');
|
|
173
|
+
|
|
174
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
175
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
176
|
+
const aliases = updatedTokenSet.colors.alias.aliases;
|
|
177
|
+
|
|
178
|
+
// Ensure the semantic token exists in the aliases structure
|
|
179
|
+
if (!aliases[firstSemanticToken]) {
|
|
180
|
+
aliases[firstSemanticToken] = {};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Ensure the 'root' condition exists
|
|
184
|
+
if (!aliases[firstSemanticToken].root) {
|
|
185
|
+
aliases[firstSemanticToken].root = [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add the new alias to the 'root' condition
|
|
189
|
+
aliases[firstSemanticToken].root.push(aliasName);
|
|
190
|
+
|
|
191
|
+
// Update the state
|
|
192
|
+
this.tokenSet = updatedTokenSet;
|
|
193
|
+
|
|
194
|
+
// Save and render changes
|
|
195
|
+
this.debouncedSaveAndRender();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private handleSearchChange(e: Event): void {
|
|
199
|
+
this.searchTerm = (e.target as HTMLInputElement).value;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private renderSemanticTokenSelect(
|
|
203
|
+
condition: 'root' | 'attention',
|
|
204
|
+
currentValue: string | undefined,
|
|
205
|
+
onChange: (value: string) => void,
|
|
206
|
+
) {
|
|
207
|
+
const semanticTokenNames = this.getSemanticTokenNames();
|
|
208
|
+
|
|
209
|
+
return html`
|
|
210
|
+
<label class="sr-only">${condition === 'root' ? 'Root' : 'Attention'}:</label>
|
|
211
|
+
<select
|
|
212
|
+
class="semantic-token-select dx-focus-ring"
|
|
213
|
+
.value=${currentValue || ''}
|
|
214
|
+
@change=${(e: Event) => {
|
|
215
|
+
const newValue = (e.target as HTMLSelectElement).value;
|
|
216
|
+
onChange(newValue);
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<option value="">—</option>
|
|
220
|
+
${repeat(
|
|
221
|
+
semanticTokenNames,
|
|
222
|
+
(name) => name,
|
|
223
|
+
(name) => html`<option value="${name}" ?selected=${name === currentValue}>${name}</option>`,
|
|
224
|
+
)}
|
|
225
|
+
</select>
|
|
226
|
+
`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
override connectedCallback(): void {
|
|
230
|
+
super.connectedCallback();
|
|
231
|
+
saveAndRender(this.tokenSet);
|
|
232
|
+
window.addEventListener(tokenSetUpdateEvent, this.handleTokenSetUpdate);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
override disconnectedCallback(): void {
|
|
236
|
+
super.disconnectedCallback();
|
|
237
|
+
window.removeEventListener(tokenSetUpdateEvent, this.handleTokenSetUpdate);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
override render() {
|
|
241
|
+
const aliasTokens = this.getUniqueAliasTokens();
|
|
242
|
+
const filteredTokens = aliasTokens.filter((token) =>
|
|
243
|
+
token.name.toLowerCase().includes(this.searchTerm.toLowerCase()),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
return html`
|
|
247
|
+
<input
|
|
248
|
+
type="search"
|
|
249
|
+
class="token-search dx-focus-ring"
|
|
250
|
+
placeholder="Search alias tokens…"
|
|
251
|
+
.value=${this.searchTerm}
|
|
252
|
+
@input=${this.handleSearchChange}
|
|
253
|
+
aria-label="Search tokens"
|
|
254
|
+
/>
|
|
255
|
+
<div class="alias-token-list">
|
|
256
|
+
<div class="alias-token-labels">
|
|
257
|
+
<span>Name</span>
|
|
258
|
+
<span>Root</span>
|
|
259
|
+
<span>Attention</span>
|
|
260
|
+
</div>
|
|
261
|
+
${repeat(
|
|
262
|
+
filteredTokens,
|
|
263
|
+
(token) => token.name,
|
|
264
|
+
(token) => html`
|
|
265
|
+
<div class="alias-token-item">
|
|
266
|
+
<input
|
|
267
|
+
type="text"
|
|
268
|
+
class="alias-name-input dx-focus-ring"
|
|
269
|
+
.value=${token.name}
|
|
270
|
+
@change=${(e: Event) => {
|
|
271
|
+
const newName = (e.target as HTMLInputElement).value;
|
|
272
|
+
this.updateAliasToken(token.name, newName, token.root, token.attention);
|
|
273
|
+
}}
|
|
274
|
+
aria-label="Alias token name"
|
|
275
|
+
/>
|
|
276
|
+
<div class="condition-selector">
|
|
277
|
+
${this.renderSemanticTokenSelect('root', token.root, (newValue) => {
|
|
278
|
+
this.updateAliasToken(token.name, token.name, newValue, token.attention);
|
|
279
|
+
})}
|
|
280
|
+
</div>
|
|
281
|
+
<div class="condition-selector">
|
|
282
|
+
${this.renderSemanticTokenSelect('attention', token.attention, (newValue) => {
|
|
283
|
+
this.updateAliasToken(token.name, token.name, token.root, newValue);
|
|
284
|
+
})}
|
|
285
|
+
</div>
|
|
286
|
+
<button
|
|
287
|
+
class="remove-alias-button dx-focus-ring dx-button"
|
|
288
|
+
@click=${() => this.removeAliasToken(token.name)}
|
|
289
|
+
aria-label="Remove alias token"
|
|
290
|
+
>
|
|
291
|
+
<span class="sr-only">Remove alias token</span>
|
|
292
|
+
<dx-icon icon="ph--minus--regular" />
|
|
293
|
+
</button>
|
|
294
|
+
</div>
|
|
295
|
+
`,
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
<button class="add-alias-button dx-focus-ring dx-button" @click=${this.addAliasToken}>Add alias</button>
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
override createRenderRoot(): this {
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { cssGradientFromCurve, helicalArcFromConfig } from '@ch-ui/colors';
|
|
6
|
+
import { type ResolvedHelicalArcSeries, type TokenSet } 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
|
+
|
|
14
|
+
import { restore, saveAndRender, tokenSetUpdateEvent } from './util';
|
|
15
|
+
|
|
16
|
+
import './dx-range-spinbutton';
|
|
17
|
+
|
|
18
|
+
export type DxThemeEditorPhysicalColorsProps = {};
|
|
19
|
+
|
|
20
|
+
const bindSeriesDefinitions = ['neutral', 'primary'];
|
|
21
|
+
|
|
22
|
+
const isHelicalArcSeries = (o: any): o is ResolvedHelicalArcSeries => {
|
|
23
|
+
return 'keyPoint' in (o ?? {});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
@customElement('dx-theme-editor-physical-colors')
|
|
27
|
+
export class DxThemeEditorPhysicalColors extends LitElement {
|
|
28
|
+
@state()
|
|
29
|
+
tokenSet: TokenSet = restore();
|
|
30
|
+
|
|
31
|
+
private debouncedSaveAndRender = debounce(() => {
|
|
32
|
+
saveAndRender(this.tokenSet);
|
|
33
|
+
}, 200);
|
|
34
|
+
|
|
35
|
+
private handleTokenSetUpdate = () => {
|
|
36
|
+
this.tokenSet = restore();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
private updateSeriesProperty(series: string, property: string, value: any): void {
|
|
40
|
+
if (!this.tokenSet.colors?.physical?.definitions?.series?.[series]) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create a deep copy of the tokenSet to avoid direct mutation
|
|
45
|
+
const updatedTokenSet = JSON.parse(JSON.stringify(this.tokenSet));
|
|
46
|
+
|
|
47
|
+
// Update the specific property
|
|
48
|
+
updatedTokenSet.colors.physical.definitions.series[series][property] = value;
|
|
49
|
+
|
|
50
|
+
// Update the state
|
|
51
|
+
this.tokenSet = updatedTokenSet;
|
|
52
|
+
|
|
53
|
+
// Save and render changes
|
|
54
|
+
this.debouncedSaveAndRender();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private handleKeyPointChange(series: string, index: number, value: number): void {
|
|
58
|
+
if (!isHelicalArcSeries(this.tokenSet.colors?.physical?.definitions?.series?.[series])) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const keyPoint = [...this.tokenSet.colors.physical.definitions.series[series].keyPoint];
|
|
63
|
+
keyPoint[index] = value;
|
|
64
|
+
|
|
65
|
+
this.updateSeriesProperty(series, 'keyPoint', keyPoint);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private handleControlPointChange(series: string, property: 'lowerCp' | 'upperCp', value: number): void {
|
|
69
|
+
this.updateSeriesProperty(series, property, value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private handleTorsionChange(series: string, value: number): void {
|
|
73
|
+
this.updateSeriesProperty(series, 'torsion', value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private renderSeriesControls(series: string) {
|
|
77
|
+
const seriesData = this.tokenSet.colors?.physical?.definitions?.series?.[series];
|
|
78
|
+
if (!isHelicalArcSeries(seriesData)) {
|
|
79
|
+
return html`<div>Series ${series} not found</div>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const keyPoint = seriesData.keyPoint || [0, 0, 0];
|
|
83
|
+
const lowerCp = seriesData.lowerCp || 0;
|
|
84
|
+
const upperCp = seriesData.upperCp || 0;
|
|
85
|
+
const torsion = seriesData.torsion || 0;
|
|
86
|
+
|
|
87
|
+
// Create unique IDs for headings to reference in aria-labelledby
|
|
88
|
+
const keyColorHeadingId = `${series}-key-color-heading`;
|
|
89
|
+
const controlPointsHeadingId = `${series}-control-points-heading`;
|
|
90
|
+
const torsionHeadingId = `${series}-torsion-heading`;
|
|
91
|
+
|
|
92
|
+
return html`
|
|
93
|
+
<div class="series-controls">
|
|
94
|
+
<div
|
|
95
|
+
class="series-preview"
|
|
96
|
+
style=${styleMap({
|
|
97
|
+
backgroundImage: cssGradientFromCurve(helicalArcFromConfig(seriesData), 21, [0, 1], 'p3'),
|
|
98
|
+
})}
|
|
99
|
+
></div>
|
|
100
|
+
<h3 class="series-title">${series} Series</h3>
|
|
101
|
+
|
|
102
|
+
<dx-range-spinbutton
|
|
103
|
+
label="Hue (0-360)"
|
|
104
|
+
min="0"
|
|
105
|
+
max="360"
|
|
106
|
+
step="0.5"
|
|
107
|
+
.value=${keyPoint[2]}
|
|
108
|
+
headingId=${keyColorHeadingId}
|
|
109
|
+
@value-changed=${(e: CustomEvent) => this.handleKeyPointChange(series, 2, e.detail.value)}
|
|
110
|
+
></dx-range-spinbutton>
|
|
111
|
+
<dx-range-spinbutton
|
|
112
|
+
label="Torsion (-180 to 180)"
|
|
113
|
+
min="-180"
|
|
114
|
+
max="180"
|
|
115
|
+
step="0.5"
|
|
116
|
+
.value=${torsion}
|
|
117
|
+
headingId=${torsionHeadingId}
|
|
118
|
+
variant="torsion"
|
|
119
|
+
@value-changed=${(e: CustomEvent) => this.handleTorsionChange(series, e.detail.value)}
|
|
120
|
+
></dx-range-spinbutton>
|
|
121
|
+
<dx-range-spinbutton
|
|
122
|
+
label="Chroma (0-0.4)"
|
|
123
|
+
min="0"
|
|
124
|
+
max="0.4"
|
|
125
|
+
step="0.001"
|
|
126
|
+
.value=${keyPoint[1]}
|
|
127
|
+
headingId=${keyColorHeadingId}
|
|
128
|
+
@value-changed=${(e: CustomEvent) => this.handleKeyPointChange(series, 1, e.detail.value)}
|
|
129
|
+
></dx-range-spinbutton>
|
|
130
|
+
|
|
131
|
+
<div class="control-group">
|
|
132
|
+
<dx-range-spinbutton
|
|
133
|
+
label="Dark Control Point (0-1)"
|
|
134
|
+
min="0"
|
|
135
|
+
max="1"
|
|
136
|
+
step="0.01"
|
|
137
|
+
.value=${upperCp}
|
|
138
|
+
headingId=${controlPointsHeadingId}
|
|
139
|
+
variant="reverse-range"
|
|
140
|
+
@value-changed=${(e: CustomEvent) => this.handleControlPointChange(series, 'upperCp', e.detail.value)}
|
|
141
|
+
></dx-range-spinbutton>
|
|
142
|
+
<dx-range-spinbutton
|
|
143
|
+
label="Light Control Point (0-1)"
|
|
144
|
+
min="0"
|
|
145
|
+
max="1"
|
|
146
|
+
step="0.01"
|
|
147
|
+
.value=${lowerCp}
|
|
148
|
+
headingId=${controlPointsHeadingId}
|
|
149
|
+
variant="reverse-order"
|
|
150
|
+
@value-changed=${(e: CustomEvent) => this.handleControlPointChange(series, 'lowerCp', e.detail.value)}
|
|
151
|
+
></dx-range-spinbutton>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override connectedCallback(): void {
|
|
158
|
+
super.connectedCallback();
|
|
159
|
+
saveAndRender(this.tokenSet);
|
|
160
|
+
window.addEventListener(tokenSetUpdateEvent, this.handleTokenSetUpdate);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
override disconnectedCallback(): void {
|
|
164
|
+
super.disconnectedCallback();
|
|
165
|
+
window.removeEventListener(tokenSetUpdateEvent, this.handleTokenSetUpdate);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override render() {
|
|
169
|
+
return repeat(
|
|
170
|
+
bindSeriesDefinitions,
|
|
171
|
+
(series) => series,
|
|
172
|
+
(series) => this.renderSeriesControls(series),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
override createRenderRoot(): this {
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
}
|