@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.
Files changed (87) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +3 -0
  3. package/dist/src/dx-theme-editor/dx-range-spinbutton.d.ts +16 -0
  4. package/dist/src/dx-theme-editor/dx-range-spinbutton.d.ts.map +1 -0
  5. package/dist/src/dx-theme-editor/dx-range-spinbutton.js +127 -0
  6. package/dist/src/dx-theme-editor/dx-range-spinbutton.js.map +1 -0
  7. package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts +21 -0
  8. package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts.map +1 -0
  9. package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.js +267 -0
  10. package/dist/src/dx-theme-editor/dx-theme-editor-alias-colors.js.map +1 -0
  11. package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts +19 -0
  12. package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts.map +1 -0
  13. package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.js +163 -0
  14. package/dist/src/dx-theme-editor/dx-theme-editor-physical-colors.js.map +1 -0
  15. package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts +32 -0
  16. package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts.map +1 -0
  17. package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.js +474 -0
  18. package/dist/src/dx-theme-editor/dx-theme-editor-semantic-colors.js.map +1 -0
  19. package/dist/src/dx-theme-editor/dx-theme-editor.d.ts +16 -0
  20. package/dist/src/dx-theme-editor/dx-theme-editor.d.ts.map +1 -0
  21. package/dist/src/dx-theme-editor/dx-theme-editor.js +160 -0
  22. package/dist/src/dx-theme-editor/dx-theme-editor.js.map +1 -0
  23. package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts +22 -0
  24. package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts.map +1 -0
  25. package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.js +27 -0
  26. package/dist/src/dx-theme-editor/dx-theme-editor.lit-stories.js.map +1 -0
  27. package/dist/src/dx-theme-editor/index.d.ts +5 -0
  28. package/dist/src/dx-theme-editor/index.d.ts.map +1 -0
  29. package/dist/src/dx-theme-editor/index.js +8 -0
  30. package/dist/src/dx-theme-editor/index.js.map +1 -0
  31. package/dist/src/dx-theme-editor/util.d.ts +8 -0
  32. package/dist/src/dx-theme-editor/util.d.ts.map +1 -0
  33. package/dist/src/dx-theme-editor/util.js +61 -0
  34. package/dist/src/dx-theme-editor/util.js.map +1 -0
  35. package/dist/src/index.d.ts +2 -0
  36. package/dist/src/index.d.ts.map +1 -0
  37. package/dist/src/index.js +5 -0
  38. package/dist/src/index.js.map +1 -0
  39. package/dist/tsconfig.tsbuildinfo +1 -0
  40. package/dist/types/src/dx-theme-editor/dx-range-spinbutton.d.ts +16 -0
  41. package/dist/types/src/dx-theme-editor/dx-range-spinbutton.d.ts.map +1 -0
  42. package/dist/types/src/dx-theme-editor/dx-range-spinbutton.js +127 -0
  43. package/dist/types/src/dx-theme-editor/dx-range-spinbutton.js.map +1 -0
  44. package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts +21 -0
  45. package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.d.ts.map +1 -0
  46. package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.js +267 -0
  47. package/dist/types/src/dx-theme-editor/dx-theme-editor-alias-colors.js.map +1 -0
  48. package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts +19 -0
  49. package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.d.ts.map +1 -0
  50. package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.js +163 -0
  51. package/dist/types/src/dx-theme-editor/dx-theme-editor-physical-colors.js.map +1 -0
  52. package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts +32 -0
  53. package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.d.ts.map +1 -0
  54. package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.js +474 -0
  55. package/dist/types/src/dx-theme-editor/dx-theme-editor-semantic-colors.js.map +1 -0
  56. package/dist/types/src/dx-theme-editor/dx-theme-editor.d.ts +16 -0
  57. package/dist/types/src/dx-theme-editor/dx-theme-editor.d.ts.map +1 -0
  58. package/dist/types/src/dx-theme-editor/dx-theme-editor.js +160 -0
  59. package/dist/types/src/dx-theme-editor/dx-theme-editor.js.map +1 -0
  60. package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts +22 -0
  61. package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.d.ts.map +1 -0
  62. package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.js +27 -0
  63. package/dist/types/src/dx-theme-editor/dx-theme-editor.lit-stories.js.map +1 -0
  64. package/dist/types/src/dx-theme-editor/index.d.ts +5 -0
  65. package/dist/types/src/dx-theme-editor/index.d.ts.map +1 -0
  66. package/dist/types/src/dx-theme-editor/index.js +8 -0
  67. package/dist/types/src/dx-theme-editor/index.js.map +1 -0
  68. package/dist/types/src/dx-theme-editor/util.d.ts +8 -0
  69. package/dist/types/src/dx-theme-editor/util.d.ts.map +1 -0
  70. package/dist/types/src/dx-theme-editor/util.js +61 -0
  71. package/dist/types/src/dx-theme-editor/util.js.map +1 -0
  72. package/dist/types/src/index.d.ts +2 -0
  73. package/dist/types/src/index.d.ts.map +1 -0
  74. package/dist/types/src/index.js +5 -0
  75. package/dist/types/src/index.js.map +1 -0
  76. package/dist/types/tsconfig.tsbuildinfo +1 -0
  77. package/package.json +40 -0
  78. package/src/dx-theme-editor/dx-range-spinbutton.ts +124 -0
  79. package/src/dx-theme-editor/dx-theme-editor-alias-colors.ts +305 -0
  80. package/src/dx-theme-editor/dx-theme-editor-physical-colors.ts +179 -0
  81. package/src/dx-theme-editor/dx-theme-editor-semantic-colors.ts +558 -0
  82. package/src/dx-theme-editor/dx-theme-editor.lit-stories.ts +37 -0
  83. package/src/dx-theme-editor/dx-theme-editor.pcss +299 -0
  84. package/src/dx-theme-editor/dx-theme-editor.ts +158 -0
  85. package/src/dx-theme-editor/index.ts +8 -0
  86. package/src/dx-theme-editor/util.ts +66 -0
  87. 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="">&mdash;</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
+ }