@pie-players/pie-tool-theme 0.1.9
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/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/tool-color-scheme.js +5049 -0
- package/index.ts +8 -0
- package/package.json +75 -0
- package/tool-color-scheme.svelte +683 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
<svelte:options
|
|
2
|
+
customElement={{
|
|
3
|
+
tag: 'pie-tool-theme',
|
|
4
|
+
shadow: 'open',
|
|
5
|
+
props: {
|
|
6
|
+
visible: { type: 'Boolean', attribute: 'visible' },
|
|
7
|
+
toolId: { type: 'String', attribute: 'tool-id' },
|
|
8
|
+
schemes: { type: 'String', attribute: 'schemes' },
|
|
9
|
+
schemeCatalog: { type: 'Object', reflect: false }
|
|
10
|
+
}
|
|
11
|
+
}}
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<!-- ToolColorScheme - WCAG Color Scheme Selection Tool
|
|
15
|
+
|
|
16
|
+
Provides accessible color scheme options for users who need
|
|
17
|
+
high contrast or alternative color combinations.
|
|
18
|
+
|
|
19
|
+
Addresses WCAG 2.1 Level AA criteria:
|
|
20
|
+
- 1.4.1 Use of Color
|
|
21
|
+
- 1.4.3 Contrast (Minimum)
|
|
22
|
+
- 1.4.11 Non-text Contrast
|
|
23
|
+
-->
|
|
24
|
+
|
|
25
|
+
<script lang="ts">
|
|
26
|
+
const browser = typeof window !== "undefined";
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
connectToolRuntimeContext,
|
|
30
|
+
ZIndexLayer,
|
|
31
|
+
} from '@pie-players/pie-assessment-toolkit';
|
|
32
|
+
import {
|
|
33
|
+
listPieColorSchemes,
|
|
34
|
+
type PieColorSchemeDefinition,
|
|
35
|
+
} from '@pie-players/pie-theme';
|
|
36
|
+
import type {
|
|
37
|
+
AssessmentToolkitRuntimeContext,
|
|
38
|
+
IToolCoordinator,
|
|
39
|
+
} from '@pie-players/pie-assessment-toolkit';
|
|
40
|
+
import { createFocusTrap, safeLocalStorageGet, safeLocalStorageSet } from '@pie-players/pie-players-shared';
|
|
41
|
+
import { onMount } from 'svelte';
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
visible = false,
|
|
45
|
+
toolId = 'theme',
|
|
46
|
+
schemes = '',
|
|
47
|
+
schemeCatalog = null
|
|
48
|
+
}: {
|
|
49
|
+
visible?: boolean;
|
|
50
|
+
toolId?: string;
|
|
51
|
+
schemes?: string;
|
|
52
|
+
schemeCatalog?: unknown;
|
|
53
|
+
} = $props();
|
|
54
|
+
|
|
55
|
+
let containerEl = $state<HTMLDivElement | undefined>();
|
|
56
|
+
let runtimeContext = $state<AssessmentToolkitRuntimeContext | null>(null);
|
|
57
|
+
const coordinator = $derived(
|
|
58
|
+
runtimeContext?.toolCoordinator as IToolCoordinator | undefined,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Track registration state
|
|
62
|
+
let registered = $state(false);
|
|
63
|
+
|
|
64
|
+
$effect(() => {
|
|
65
|
+
if (!containerEl) return;
|
|
66
|
+
return connectToolRuntimeContext(containerEl, (value: AssessmentToolkitRuntimeContext) => {
|
|
67
|
+
runtimeContext = value;
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
type ToolSchemePreview = {
|
|
72
|
+
bg: string;
|
|
73
|
+
text: string;
|
|
74
|
+
primary: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type ToolColorSchemeOption = {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
description: string;
|
|
81
|
+
preview: ToolSchemePreview;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const DEFAULT_COLOR_SCHEMES: ToolColorSchemeOption[] = [
|
|
85
|
+
{
|
|
86
|
+
id: 'default',
|
|
87
|
+
name: 'Default',
|
|
88
|
+
description: 'Standard PIE colors',
|
|
89
|
+
preview: {
|
|
90
|
+
bg: '#ffffff',
|
|
91
|
+
text: '#000000',
|
|
92
|
+
primary: '#3f51b5'
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'black-on-white',
|
|
97
|
+
name: 'Black on White',
|
|
98
|
+
description: 'High contrast for readability',
|
|
99
|
+
preview: {
|
|
100
|
+
bg: '#ffffff',
|
|
101
|
+
text: '#000000',
|
|
102
|
+
primary: '#0000cc'
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'white-on-black',
|
|
107
|
+
name: 'White on Black',
|
|
108
|
+
description: 'Inverse high contrast',
|
|
109
|
+
preview: {
|
|
110
|
+
bg: '#000000',
|
|
111
|
+
text: '#ffffff',
|
|
112
|
+
primary: '#ffff00'
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'rose-on-green',
|
|
117
|
+
name: 'Rose on Green',
|
|
118
|
+
description: 'Color blind friendly (protanopia/deuteranopia)',
|
|
119
|
+
preview: {
|
|
120
|
+
bg: '#ccffcc',
|
|
121
|
+
text: '#3d0022',
|
|
122
|
+
primary: '#660044'
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'yellow-on-blue',
|
|
127
|
+
name: 'Yellow on Blue',
|
|
128
|
+
description: 'Strong contrast scheme',
|
|
129
|
+
preview: {
|
|
130
|
+
bg: '#000066',
|
|
131
|
+
text: '#ffff00',
|
|
132
|
+
primary: '#ffff66'
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'black-on-rose',
|
|
137
|
+
name: 'Black on Rose',
|
|
138
|
+
description: 'Warm tinted background',
|
|
139
|
+
preview: {
|
|
140
|
+
bg: '#ffccdd',
|
|
141
|
+
text: '#000000',
|
|
142
|
+
primary: '#880044'
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'light-gray-on-dark-gray',
|
|
147
|
+
name: 'Light Gray on Dark Gray',
|
|
148
|
+
description: 'Reduced brightness for light sensitivity',
|
|
149
|
+
preview: {
|
|
150
|
+
bg: '#333333',
|
|
151
|
+
text: '#e0e0e0',
|
|
152
|
+
primary: '#aaaaaa'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
function previewFromVariables(
|
|
158
|
+
variables: Record<string, string> | undefined,
|
|
159
|
+
fallback: ToolSchemePreview
|
|
160
|
+
): ToolSchemePreview {
|
|
161
|
+
return {
|
|
162
|
+
bg: variables?.['--pie-background'] || variables?.['--pie-secondary-background'] || fallback.bg,
|
|
163
|
+
text: variables?.['--pie-text'] || fallback.text,
|
|
164
|
+
primary: variables?.['--pie-primary'] || fallback.primary
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeSchemeCatalogEntry(entry: unknown): ToolColorSchemeOption | null {
|
|
169
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
170
|
+
const value = entry as Record<string, unknown>;
|
|
171
|
+
const id = typeof value.id === 'string' ? value.id.trim() : '';
|
|
172
|
+
if (!id) return null;
|
|
173
|
+
const name = typeof value.name === 'string' && value.name.trim() ? value.name.trim() : id;
|
|
174
|
+
const description = typeof value.description === 'string' ? value.description : '';
|
|
175
|
+
const previewValue = value.preview;
|
|
176
|
+
const preview = (
|
|
177
|
+
previewValue &&
|
|
178
|
+
typeof previewValue === 'object' &&
|
|
179
|
+
typeof (previewValue as Record<string, unknown>).bg === 'string' &&
|
|
180
|
+
typeof (previewValue as Record<string, unknown>).text === 'string' &&
|
|
181
|
+
typeof (previewValue as Record<string, unknown>).primary === 'string'
|
|
182
|
+
)
|
|
183
|
+
? {
|
|
184
|
+
bg: (previewValue as Record<string, string>).bg,
|
|
185
|
+
text: (previewValue as Record<string, string>).text,
|
|
186
|
+
primary: (previewValue as Record<string, string>).primary
|
|
187
|
+
}
|
|
188
|
+
: previewFromVariables(
|
|
189
|
+
typeof value.variables === 'object' && value.variables
|
|
190
|
+
? (value.variables as Record<string, string>)
|
|
191
|
+
: undefined,
|
|
192
|
+
{ bg: '#ffffff', text: '#000000', primary: '#3f51b5' }
|
|
193
|
+
);
|
|
194
|
+
return { id, name, description, preview };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function parseSchemesFromAttribute(raw: string): ToolColorSchemeOption[] {
|
|
198
|
+
if (!raw?.trim()) return [];
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
201
|
+
return Array.isArray(parsed)
|
|
202
|
+
? parsed
|
|
203
|
+
.map((entry) => normalizeSchemeCatalogEntry(entry))
|
|
204
|
+
.filter((entry): entry is ToolColorSchemeOption => Boolean(entry))
|
|
205
|
+
: [];
|
|
206
|
+
} catch {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function resolveThemeHost(): HTMLElement | null {
|
|
212
|
+
const localHost = containerEl?.closest('pie-theme') as HTMLElement | null;
|
|
213
|
+
if (localHost) return localHost;
|
|
214
|
+
const scopedDocumentHost = document.querySelector('pie-theme[scope="document"]') as HTMLElement | null;
|
|
215
|
+
if (scopedDocumentHost) return scopedDocumentHost;
|
|
216
|
+
return document.querySelector('pie-theme') as HTMLElement | null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function resolveThemeSchemes(): ToolColorSchemeOption[] {
|
|
220
|
+
const themeSchemes = listPieColorSchemes();
|
|
221
|
+
const fallbackById = new Map(DEFAULT_COLOR_SCHEMES.map((scheme) => [scheme.id, scheme]));
|
|
222
|
+
return themeSchemes.map((scheme: PieColorSchemeDefinition) => {
|
|
223
|
+
const fallback = fallbackById.get(scheme.id) || {
|
|
224
|
+
id: scheme.id,
|
|
225
|
+
name: scheme.name || scheme.id,
|
|
226
|
+
description: scheme.description || '',
|
|
227
|
+
preview: { bg: '#ffffff', text: '#000000', primary: '#3f51b5' }
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
id: scheme.id,
|
|
231
|
+
name: scheme.name || fallback.name,
|
|
232
|
+
description: scheme.description || fallback.description || '',
|
|
233
|
+
preview: scheme.preview || previewFromVariables(scheme.variables, fallback.preview)
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const availableSchemes = $derived.by(() => {
|
|
239
|
+
if (Array.isArray(schemeCatalog) && schemeCatalog.length > 0) {
|
|
240
|
+
const normalized = schemeCatalog
|
|
241
|
+
.map((entry) => normalizeSchemeCatalogEntry(entry))
|
|
242
|
+
.filter((entry): entry is ToolColorSchemeOption => Boolean(entry));
|
|
243
|
+
if (normalized.length > 0) return normalized;
|
|
244
|
+
}
|
|
245
|
+
const parsedFromAttribute = parseSchemesFromAttribute(schemes);
|
|
246
|
+
if (parsedFromAttribute.length > 0) return parsedFromAttribute;
|
|
247
|
+
const themeSchemes = resolveThemeSchemes();
|
|
248
|
+
if (themeSchemes.length > 0) return themeSchemes;
|
|
249
|
+
return DEFAULT_COLOR_SCHEMES;
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Current color scheme
|
|
253
|
+
let currentScheme = $state('default');
|
|
254
|
+
|
|
255
|
+
// Dropdown state
|
|
256
|
+
let dropdownOpen = $state(false);
|
|
257
|
+
|
|
258
|
+
// Focus trap cleanup function (plain variable, not reactive)
|
|
259
|
+
let cleanupFocusTrap: (() => void) | null = null;
|
|
260
|
+
|
|
261
|
+
// Apply color scheme to document
|
|
262
|
+
function applyColorScheme(schemeId: string) {
|
|
263
|
+
if (!browser) return;
|
|
264
|
+
|
|
265
|
+
const themeHost = resolveThemeHost();
|
|
266
|
+
if (themeHost) {
|
|
267
|
+
themeHost.setAttribute('scheme', schemeId || 'default');
|
|
268
|
+
} else {
|
|
269
|
+
const root = document.documentElement;
|
|
270
|
+
if (schemeId === 'default') {
|
|
271
|
+
root.removeAttribute('data-color-scheme');
|
|
272
|
+
} else {
|
|
273
|
+
root.setAttribute('data-color-scheme', schemeId);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Save to localStorage safely
|
|
278
|
+
safeLocalStorageSet('pie-color-scheme', schemeId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Select scheme and close the tool
|
|
282
|
+
// Automatically closes the modal after selection for better UX
|
|
283
|
+
function selectScheme(schemeId: string) {
|
|
284
|
+
currentScheme = schemeId;
|
|
285
|
+
applyColorScheme(schemeId);
|
|
286
|
+
// Close the entire tool modal and deselect toolbar button
|
|
287
|
+
coordinator?.hideTool(toolId);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Toggle dropdown
|
|
291
|
+
function toggleDropdown() {
|
|
292
|
+
dropdownOpen = !dropdownOpen;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get current scheme object
|
|
296
|
+
let currentSchemeObj = $derived(
|
|
297
|
+
availableSchemes.find(s => s.id === currentScheme) ||
|
|
298
|
+
availableSchemes[0] ||
|
|
299
|
+
DEFAULT_COLOR_SCHEMES[0]
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
$effect(() => {
|
|
303
|
+
if (!availableSchemes.find((scheme) => scheme.id === currentScheme)) {
|
|
304
|
+
currentScheme = availableSchemes[0]?.id || 'default';
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
function handleClose() {
|
|
309
|
+
dropdownOpen = false;
|
|
310
|
+
coordinator?.hideTool(toolId);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Handle escape key
|
|
314
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
315
|
+
if (e.key === 'Escape') {
|
|
316
|
+
if (dropdownOpen) {
|
|
317
|
+
// Close dropdown first
|
|
318
|
+
dropdownOpen = false;
|
|
319
|
+
} else {
|
|
320
|
+
// Close the entire tool
|
|
321
|
+
handleClose();
|
|
322
|
+
}
|
|
323
|
+
} else if (dropdownOpen && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) {
|
|
324
|
+
// Handle arrow key navigation in dropdown
|
|
325
|
+
e.preventDefault();
|
|
326
|
+
const options = containerEl?.querySelectorAll('.pie-tool-color-scheme__option:not([disabled])') as NodeListOf<HTMLButtonElement>;
|
|
327
|
+
if (!options || options.length === 0) return;
|
|
328
|
+
|
|
329
|
+
const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);
|
|
330
|
+
let nextIndex: number;
|
|
331
|
+
|
|
332
|
+
if (e.key === 'ArrowDown') {
|
|
333
|
+
nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % options.length;
|
|
334
|
+
} else {
|
|
335
|
+
nextIndex = currentIndex === -1 ? options.length - 1 : (currentIndex - 1 + options.length) % options.length;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
options[nextIndex]?.focus();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Register with coordinator when it becomes available
|
|
343
|
+
$effect(() => {
|
|
344
|
+
if (coordinator && toolId && !registered) {
|
|
345
|
+
coordinator.registerTool(toolId, 'Theme', undefined, ZIndexLayer.MODAL);
|
|
346
|
+
registered = true;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Update element reference when container becomes available
|
|
351
|
+
$effect(() => {
|
|
352
|
+
if (coordinator && containerEl && toolId) {
|
|
353
|
+
coordinator.updateToolElement(toolId, containerEl);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Handle focus trap cleanup when visibility changes
|
|
358
|
+
$effect(() => {
|
|
359
|
+
if (!visible && cleanupFocusTrap) {
|
|
360
|
+
cleanupFocusTrap();
|
|
361
|
+
cleanupFocusTrap = null;
|
|
362
|
+
} else if (visible && containerEl && !cleanupFocusTrap) {
|
|
363
|
+
cleanupFocusTrap = createFocusTrap(containerEl);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
onMount(() => {
|
|
368
|
+
// Load saved scheme from localStorage safely
|
|
369
|
+
if (browser) {
|
|
370
|
+
const themeHost = resolveThemeHost();
|
|
371
|
+
const hostScheme = themeHost?.getAttribute('scheme');
|
|
372
|
+
const saved = safeLocalStorageGet('pie-color-scheme') ?? hostScheme ?? 'default';
|
|
373
|
+
currentScheme = saved;
|
|
374
|
+
if (saved) {
|
|
375
|
+
requestAnimationFrame(() => {
|
|
376
|
+
applyColorScheme(saved);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Click outside handler
|
|
382
|
+
function handleClickOutside(e: MouseEvent) {
|
|
383
|
+
if (!dropdownOpen) return;
|
|
384
|
+
const clickPath = e.composedPath();
|
|
385
|
+
if (!containerEl || !clickPath.includes(containerEl)) {
|
|
386
|
+
dropdownOpen = false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
document.addEventListener('click', handleClickOutside);
|
|
391
|
+
|
|
392
|
+
return () => {
|
|
393
|
+
document.removeEventListener('click', handleClickOutside);
|
|
394
|
+
if (coordinator && toolId) {
|
|
395
|
+
coordinator.unregisterTool(toolId);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
</script>
|
|
400
|
+
|
|
401
|
+
{#if visible}
|
|
402
|
+
<div bind:this={containerEl} class="pie-tool-color-scheme" role="dialog" tabindex="-1" aria-modal="true" aria-labelledby="color-scheme-title" onkeydown={handleKeyDown}>
|
|
403
|
+
<div class="pie-tool-color-scheme__header">
|
|
404
|
+
<h3 id="color-scheme-title" class="pie-tool-color-scheme__title">Theme</h3>
|
|
405
|
+
<button
|
|
406
|
+
type="button"
|
|
407
|
+
class="pie-tool-color-scheme__close"
|
|
408
|
+
onclick={handleClose}
|
|
409
|
+
aria-label="Close theme selector"
|
|
410
|
+
>
|
|
411
|
+
×
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<div class="pie-tool-color-scheme__content">
|
|
416
|
+
<p class="pie-tool-color-scheme__description">
|
|
417
|
+
Select a theme to improve readability and reduce eye strain.
|
|
418
|
+
</p>
|
|
419
|
+
|
|
420
|
+
<button
|
|
421
|
+
type="button"
|
|
422
|
+
class="pie-tool-color-scheme__dropdown-trigger"
|
|
423
|
+
aria-label="Select theme"
|
|
424
|
+
aria-expanded={dropdownOpen}
|
|
425
|
+
onclick={toggleDropdown}
|
|
426
|
+
>
|
|
427
|
+
<div class="pie-tool-color-scheme__current">
|
|
428
|
+
<div class="pie-tool-color-scheme__preview">
|
|
429
|
+
<div class="pie-tool-color-scheme__preview-bg" style="background-color: {currentSchemeObj.preview.bg}">
|
|
430
|
+
<div class="pie-tool-color-scheme__preview-text" style="color: {currentSchemeObj.preview.text}">A</div>
|
|
431
|
+
<div class="pie-tool-color-scheme__preview-primary" style="background-color: {currentSchemeObj.preview.primary}"></div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="pie-tool-color-scheme__current-info">
|
|
435
|
+
<div class="pie-tool-color-scheme__current-name">{currentSchemeObj.name}</div>
|
|
436
|
+
<div class="pie-tool-color-scheme__current-description">{currentSchemeObj.description}</div>
|
|
437
|
+
</div>
|
|
438
|
+
<svg class="pie-tool-color-scheme__dropdown-arrow" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
439
|
+
<path d="M7,10L12,15L17,10H7Z"/>
|
|
440
|
+
</svg>
|
|
441
|
+
</div>
|
|
442
|
+
</button>
|
|
443
|
+
|
|
444
|
+
{#if dropdownOpen}
|
|
445
|
+
<div class="pie-tool-color-scheme__dropdown" role="menu">
|
|
446
|
+
{#each availableSchemes as scheme (scheme.id)}
|
|
447
|
+
<button
|
|
448
|
+
type="button"
|
|
449
|
+
class="pie-tool-color-scheme__option"
|
|
450
|
+
class:pie-tool-color-scheme__option--active={currentScheme === scheme.id}
|
|
451
|
+
role="menuitem"
|
|
452
|
+
aria-label={scheme.name}
|
|
453
|
+
aria-current={currentScheme === scheme.id}
|
|
454
|
+
onclick={() => selectScheme(scheme.id)}
|
|
455
|
+
>
|
|
456
|
+
<div class="pie-tool-color-scheme__preview">
|
|
457
|
+
<div class="pie-tool-color-scheme__preview-bg" style="background-color: {scheme.preview.bg}">
|
|
458
|
+
<div class="pie-tool-color-scheme__preview-text" style="color: {scheme.preview.text}">A</div>
|
|
459
|
+
<div class="pie-tool-color-scheme__preview-primary" style="background-color: {scheme.preview.primary}"></div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="pie-tool-color-scheme__info">
|
|
463
|
+
<div class="pie-tool-color-scheme__name">{scheme.name}</div>
|
|
464
|
+
<div class="pie-tool-color-scheme__description">{scheme.description}</div>
|
|
465
|
+
</div>
|
|
466
|
+
{#if currentScheme === scheme.id}
|
|
467
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="pie-tool-color-scheme__check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
468
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
469
|
+
</svg>
|
|
470
|
+
{/if}
|
|
471
|
+
</button>
|
|
472
|
+
{/each}
|
|
473
|
+
</div>
|
|
474
|
+
{/if}
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
{/if}
|
|
478
|
+
|
|
479
|
+
<style>
|
|
480
|
+
.pie-tool-color-scheme {
|
|
481
|
+
position: fixed;
|
|
482
|
+
top: 50%;
|
|
483
|
+
left: 50%;
|
|
484
|
+
transform: translate(-50%, -50%);
|
|
485
|
+
width: 90%;
|
|
486
|
+
max-width: 32rem;
|
|
487
|
+
background-color: var(--pie-background, white);
|
|
488
|
+
border: 2px solid var(--pie-border, #ccc);
|
|
489
|
+
border-radius: 0.5rem;
|
|
490
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
491
|
+
display: flex;
|
|
492
|
+
flex-direction: column;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.pie-tool-color-scheme__header {
|
|
496
|
+
display: flex;
|
|
497
|
+
justify-content: space-between;
|
|
498
|
+
align-items: center;
|
|
499
|
+
padding: 1rem;
|
|
500
|
+
border-bottom: 2px solid var(--pie-border, #ccc);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.pie-tool-color-scheme__title {
|
|
504
|
+
font-size: 1.25rem;
|
|
505
|
+
font-weight: 700;
|
|
506
|
+
color: var(--pie-text, black);
|
|
507
|
+
margin: 0;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.pie-tool-color-scheme__close {
|
|
511
|
+
width: 2rem;
|
|
512
|
+
height: 2rem;
|
|
513
|
+
border: none;
|
|
514
|
+
background: transparent;
|
|
515
|
+
color: var(--pie-text, black);
|
|
516
|
+
font-size: 1.5rem;
|
|
517
|
+
line-height: 1;
|
|
518
|
+
cursor: pointer;
|
|
519
|
+
border-radius: 0.25rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.pie-tool-color-scheme__close:hover {
|
|
523
|
+
background-color: var(--pie-secondary-background, #f0f0f0);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.pie-tool-color-scheme__content {
|
|
527
|
+
padding: 1rem;
|
|
528
|
+
position: relative;
|
|
529
|
+
display: flex;
|
|
530
|
+
flex-direction: column;
|
|
531
|
+
gap: 1rem;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.pie-tool-color-scheme__description {
|
|
535
|
+
margin: 0 0 1rem 0;
|
|
536
|
+
color: var(--pie-text, black);
|
|
537
|
+
opacity: 0.7;
|
|
538
|
+
font-size: 0.875rem;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.pie-tool-color-scheme__dropdown-trigger {
|
|
542
|
+
width: 100%;
|
|
543
|
+
padding: 0.75rem;
|
|
544
|
+
border: 2px solid var(--pie-border, #ccc);
|
|
545
|
+
border-radius: 0.5rem;
|
|
546
|
+
background-color: var(--pie-background, white);
|
|
547
|
+
color: var(--pie-text, black);
|
|
548
|
+
cursor: pointer;
|
|
549
|
+
transition: all 0.15s ease;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.pie-tool-color-scheme__dropdown-trigger:hover {
|
|
553
|
+
background-color: var(--pie-secondary-background, #f0f0f0);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.pie-tool-color-scheme__current {
|
|
557
|
+
display: flex;
|
|
558
|
+
align-items: center;
|
|
559
|
+
gap: 0.75rem;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.pie-tool-color-scheme__current-info {
|
|
563
|
+
flex: 1;
|
|
564
|
+
text-align: left;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.pie-tool-color-scheme__current-name {
|
|
568
|
+
font-size: 0.875rem;
|
|
569
|
+
font-weight: 600;
|
|
570
|
+
color: var(--pie-text, black);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.pie-tool-color-scheme__current-description {
|
|
574
|
+
font-size: 0.75rem;
|
|
575
|
+
color: var(--pie-text, black);
|
|
576
|
+
opacity: 0.6;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.pie-tool-color-scheme__dropdown-arrow {
|
|
580
|
+
width: 1.5rem;
|
|
581
|
+
height: 1.5rem;
|
|
582
|
+
flex-shrink: 0;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.pie-tool-color-scheme__dropdown {
|
|
586
|
+
position: relative;
|
|
587
|
+
width: 100%;
|
|
588
|
+
max-height: 24rem;
|
|
589
|
+
background-color: var(--pie-background, white);
|
|
590
|
+
border: 2px solid var(--pie-border, #ccc);
|
|
591
|
+
border-radius: 0.5rem;
|
|
592
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
593
|
+
overflow-y: auto;
|
|
594
|
+
overflow-x: hidden;
|
|
595
|
+
margin-top: 0.5rem;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.pie-tool-color-scheme__option {
|
|
599
|
+
display: flex;
|
|
600
|
+
align-items: center;
|
|
601
|
+
gap: 0.75rem;
|
|
602
|
+
width: 100%;
|
|
603
|
+
padding: 0.75rem 1rem;
|
|
604
|
+
border: none;
|
|
605
|
+
background-color: transparent;
|
|
606
|
+
color: var(--pie-text, black);
|
|
607
|
+
cursor: pointer;
|
|
608
|
+
text-align: left;
|
|
609
|
+
transition: background-color 0.15s ease;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.pie-tool-color-scheme__option:hover {
|
|
613
|
+
background-color: var(--pie-secondary-background, rgba(0, 0, 0, 0.05));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.pie-tool-color-scheme__option--active {
|
|
617
|
+
background-color: var(--pie-primary-light, rgba(63, 81, 181, 0.1));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.pie-tool-color-scheme__preview {
|
|
621
|
+
flex-shrink: 0;
|
|
622
|
+
width: 2.5rem;
|
|
623
|
+
height: 2.5rem;
|
|
624
|
+
border: 2px solid var(--pie-border, #ccc);
|
|
625
|
+
border-radius: 0.25rem;
|
|
626
|
+
overflow: hidden;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.pie-tool-color-scheme__preview-bg {
|
|
630
|
+
width: 100%;
|
|
631
|
+
height: 100%;
|
|
632
|
+
position: relative;
|
|
633
|
+
display: flex;
|
|
634
|
+
align-items: center;
|
|
635
|
+
justify-content: center;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.pie-tool-color-scheme__preview-text {
|
|
639
|
+
font-weight: 700;
|
|
640
|
+
font-size: 1.25rem;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.pie-tool-color-scheme__preview-primary {
|
|
644
|
+
position: absolute;
|
|
645
|
+
bottom: 0;
|
|
646
|
+
right: 0;
|
|
647
|
+
width: 0.75rem;
|
|
648
|
+
height: 0.75rem;
|
|
649
|
+
border-radius: 50%;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.pie-tool-color-scheme__info {
|
|
653
|
+
flex: 1;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.pie-tool-color-scheme__name {
|
|
657
|
+
font-size: 0.875rem;
|
|
658
|
+
font-weight: 600;
|
|
659
|
+
color: var(--pie-text, black);
|
|
660
|
+
line-height: 1.25;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.pie-tool-color-scheme__description {
|
|
664
|
+
font-size: 0.75rem;
|
|
665
|
+
color: var(--pie-text, black);
|
|
666
|
+
opacity: 0.6;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.pie-tool-color-scheme__check {
|
|
670
|
+
flex-shrink: 0;
|
|
671
|
+
width: 1rem;
|
|
672
|
+
height: 1rem;
|
|
673
|
+
color: var(--pie-primary, #3f51b5);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* Keyboard focus styling */
|
|
677
|
+
.pie-tool-color-scheme__close:focus-visible,
|
|
678
|
+
.pie-tool-color-scheme__dropdown-trigger:focus-visible,
|
|
679
|
+
.pie-tool-color-scheme__option:focus-visible {
|
|
680
|
+
outline: 2px solid var(--pie-primary, #3f51b5);
|
|
681
|
+
outline-offset: 2px;
|
|
682
|
+
}
|
|
683
|
+
</style>
|