@motion-proto/live-tokens 0.97.0 → 0.99.0
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/.claude/skills/live-tokens-check-compliance/SKILL.md +5 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +4 -3
- package/.claude/skills/live-tokens-create-component/references/contract-tests.md +71 -11
- package/CHANGELOG.md +339 -0
- package/README.md +2 -2
- package/bin/check-component.mjs +8 -0
- package/bin/check-page.mjs +6 -0
- package/bin/cli.mjs +36 -6
- package/bin/contractRunner.mjs +144 -19
- package/bin/lib/findings.mjs +20 -4
- package/bin/rules/a11y.mjs +53 -0
- package/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
- package/dist-plugin/index.cjs +204 -3
- package/dist-plugin/index.js +306 -105
- package/dist-plugin/setColors/index.js +1 -1
- package/package.json +47 -2
- package/src/demo/LabeledSelect.svelte +1 -1
- package/src/editor/bootstrap.ts +5 -1
- package/src/editor/component-editor/BadgeEditor.svelte +13 -0
- package/src/editor/component-editor/ButtonEditor.svelte +33 -2
- package/src/editor/component-editor/CalloutEditor.svelte +13 -0
- package/src/editor/component-editor/CardEditor.svelte +12 -2
- package/src/editor/component-editor/CodeSnippetEditor.svelte +11 -1
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +18 -1
- package/src/editor/component-editor/CornerBadgeEditor.svelte +13 -0
- package/src/editor/component-editor/DialogEditor.svelte +15 -0
- package/src/editor/component-editor/IconButtonEditor.svelte +29 -1
- package/src/editor/component-editor/ImageEditor.svelte +20 -9
- package/src/editor/component-editor/ImageLightboxEditor.svelte +44 -0
- package/src/editor/component-editor/InlineEditActionsEditor.svelte +13 -1
- package/src/editor/component-editor/InputEditor.svelte +23 -0
- package/src/editor/component-editor/MenuSelectEditor.svelte +27 -1
- package/src/editor/component-editor/NotificationEditor.svelte +15 -0
- package/src/editor/component-editor/PanelEditor.svelte +1 -1
- package/src/editor/component-editor/ProgressBarEditor.svelte +13 -0
- package/src/editor/component-editor/RadioButtonEditor.svelte +20 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +40 -13
- package/src/editor/component-editor/SegmentedControlEditor.svelte +29 -1
- package/src/editor/component-editor/SideNavigationEditor.svelte +40 -1
- package/src/editor/component-editor/SlidePagerEditor.svelte +20 -0
- package/src/editor/component-editor/SliderEditor.svelte +33 -0
- package/src/editor/component-editor/TabBarEditor.svelte +23 -2
- package/src/editor/component-editor/TableEditor.svelte +14 -0
- package/src/editor/component-editor/ToggleEditor.svelte +17 -0
- package/src/editor/component-editor/TooltipEditor.svelte +11 -2
- package/src/editor/component-editor/VideoFrameEditor.svelte +8 -0
- package/src/editor/component-editor/VideoLightboxEditor.svelte +10 -0
- package/src/editor/component-editor/registration.ts +2 -1
- package/src/editor/component-editor/registry.ts +69 -27
- package/src/editor/component-editor/scaffolding/DemoHeader.svelte +2 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +19 -8
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +3 -0
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +70 -3
- package/src/editor/component-editor/scaffolding/editorContext.ts +7 -0
- package/src/editor/core/a11y/a11yReport.ts +164 -0
- package/src/editor/core/a11y/a11yReportTypes.ts +77 -0
- package/src/editor/core/a11y/contrastFailures.ts +22 -0
- package/src/editor/core/a11y/contrastFixes.ts +41 -0
- package/src/editor/core/components/contrastPairRegistry.ts +18 -0
- package/src/editor/core/palettes/contrast.ts +44 -0
- package/src/editor/core/palettes/contrastPairs.ts +82 -0
- package/src/editor/core/palettes/resolveColor.ts +77 -0
- package/src/editor/core/store/editorStore.ts +4 -0
- package/src/editor/core/store/editorViewStore.ts +2 -2
- package/src/editor/core/store/unsavedEdits.ts +10 -0
- package/src/editor/core/themes/liveStateStream.ts +7 -1
- package/src/editor/core/themes/parsers/gradient.ts +5 -2
- package/src/editor/docs/chapters.ts +1 -0
- package/src/editor/docs/content/accessibility.md +411 -0
- package/src/editor/docs/content/editing-tokens.md +32 -1
- package/src/editor/docs/content.generated.ts +2 -1
- package/src/editor/pages/ComponentEditorPage.svelte +11 -1
- package/src/editor/pages/EditorShell.svelte +98 -10
- package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
- package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
- package/src/editor/skill-atlas/trees/create-component.ts +12 -12
- package/src/editor/styles/ui-editor.css +2 -0
- package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
- package/src/editor/ui/GradientEditor.svelte +9 -1
- package/src/editor/ui/ThemePanel.svelte +4 -4
- package/src/editor/ui/UIContrastReadout.svelte +186 -0
- package/src/editor/ui/UIInfoPopover.svelte +16 -2
- package/src/editor/ui/UIPaletteSelector.svelte +9 -0
- package/src/editor/ui/UITokenSelector.svelte +26 -2
- package/src/editor/ui/a11y/A11yByEye.svelte +264 -0
- package/src/editor/ui/a11y/A11yComponentIssues.svelte +121 -0
- package/src/editor/ui/a11y/A11yFindingRow.svelte +119 -0
- package/src/editor/ui/a11y/A11yIssueRow.svelte +152 -0
- package/src/editor/ui/a11y/A11yOverview.svelte +246 -0
- package/src/editor/ui/a11y/A11yPanel.svelte +153 -0
- package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
- package/src/editor/ui/a11y/A11yRailMark.svelte +41 -0
- package/src/editor/ui/a11y/AccessibilityTab.svelte +321 -0
- package/src/editor/ui/a11y/a11yView.ts +257 -0
- package/src/editor/ui/contrastReadout.ts +45 -0
- package/src/live-tokens/data/colors-and-type/halloween.json +1 -1
- package/src/live-tokens/data/themes/autumn.json +35 -35
- package/src/live-tokens/data/themes/halloween.json +36 -36
- package/src/live-tokens/data/themes/midnight-study.json +35 -35
- package/src/live-tokens/data/themes/ocean.json +35 -35
- package/src/live-tokens/data/themes/royal-velvet.json +35 -35
- package/src/live-tokens/data/themes/sketchy.json +35 -35
- package/src/live-tokens/data/themes/spring-meadow.json +35 -35
- package/src/live-tokens/data/themes/sunset.json +35 -35
- package/src/system/components/Badge.svelte +3 -3
- package/src/system/components/Button.svelte +12 -6
- package/src/system/components/CodeSnippet.svelte +8 -1
- package/src/system/components/CornerBadge.svelte +3 -3
- package/src/system/components/Dialog.svelte +43 -14
- package/src/system/components/IconButton.svelte +1 -1
- package/src/system/components/Image.svelte +7 -0
- package/src/system/components/ImageLightbox.svelte +18 -25
- package/src/system/components/InlineEditActions.svelte +1 -1
- package/src/system/components/Input.svelte +9 -2
- package/src/system/components/MenuSelect.svelte +31 -3
- package/src/system/components/Notification.svelte +1 -1
- package/src/system/components/ProgressBar.svelte +7 -1
- package/src/system/components/RadioButton.svelte +36 -3
- package/src/system/components/SegmentedControl.svelte +36 -2
- package/src/system/components/SlidePager.svelte +3 -3
- package/src/system/components/Slider.svelte +19 -15
- package/src/system/components/TabBar.svelte +33 -3
- package/src/system/components/Toggle.svelte +3 -3
- package/src/system/components/VideoFrame.svelte +1 -1
- package/src/system/components/VideoLightbox.svelte +2 -2
- package/src/system/internal/focusTrap.ts +35 -0
- package/src/testing-js/__aria__/badge.yml +40 -0
- package/src/testing-js/__aria__/button.yml +88 -0
- package/src/testing-js/__aria__/callout.yml +12 -0
- package/src/testing-js/__aria__/card.yml +8 -0
- package/src/testing-js/__aria__/codesnippet.yml +21 -0
- package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
- package/src/testing-js/__aria__/cornerbadge.yml +24 -0
- package/src/testing-js/__aria__/dialog.yml +42 -0
- package/src/testing-js/__aria__/iconbutton.yml +57 -0
- package/src/testing-js/__aria__/image.yml +10 -0
- package/src/testing-js/__aria__/imagelightbox.yml +50 -0
- package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
- package/src/testing-js/__aria__/input.yml +61 -0
- package/src/testing-js/__aria__/menuselect.yml +30 -0
- package/src/testing-js/__aria__/notification.yml +30 -0
- package/src/testing-js/__aria__/panel.yml +2 -0
- package/src/testing-js/__aria__/progressbar.yml +2 -0
- package/src/testing-js/__aria__/radiobutton.yml +20 -0
- package/src/testing-js/__aria__/sectiondivider.yml +9 -0
- package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
- package/src/testing-js/__aria__/sidenavigation.yml +304 -0
- package/src/testing-js/__aria__/slidepager.yml +72 -0
- package/src/testing-js/__aria__/slider.yml +28 -0
- package/src/testing-js/__aria__/tabbar.yml +35 -0
- package/src/testing-js/__aria__/table.yml +144 -0
- package/src/testing-js/__aria__/toggle.yml +12 -0
- package/src/testing-js/__aria__/tooltip.yml +2 -0
- package/src/testing-js/__aria__/videoframe.yml +6 -0
- package/src/testing-js/__aria__/videolightbox.yml +12 -0
- package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
- package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
- package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
- package/src/testing-js/{chunk-X6KHEVIG.js → chunk-7QXUJRSO.js} +97 -29
- package/src/testing-js/chunk-7QXUJRSO.js.map +1 -0
- package/src/testing-js/chunk-GZKUYLDW.js +193 -0
- package/src/testing-js/chunk-GZKUYLDW.js.map +1 -0
- package/src/testing-js/chunk-RTZQLW3F.js +649 -0
- package/src/testing-js/chunk-RTZQLW3F.js.map +1 -0
- package/src/testing-js/{chunk-SHBYZUPD.js → chunk-RWOT2XDB.js} +212 -15
- package/src/testing-js/chunk-RWOT2XDB.js.map +1 -0
- package/src/testing-js/{chunk-GKO27NGN.js → chunk-THWGMSRH.js} +2 -2
- package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
- package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
- package/src/testing-js/component-a11y.contract.js +313 -0
- package/src/testing-js/component-a11y.contract.js.map +1 -0
- package/src/testing-js/component-alias.contract.js +2 -2
- package/src/testing-js/component-behavior.contract.js +2 -2
- package/src/testing-js/component-editor.contract.js +6 -5
- package/src/testing-js/component-editor.contract.js.map +1 -1
- package/src/testing-js/component-render.contract.js +10 -6
- package/src/testing-js/component-render.contract.js.map +1 -1
- package/src/testing-js/index.d.ts +44 -16
- package/src/testing-js/index.js +11 -11
- package/src/testing-js/index.js.map +1 -1
- package/src/testing-js/page-compliance.contract.js +132 -13
- package/src/testing-js/page-compliance.contract.js.map +1 -1
- package/src/testing-js/vitest.js +3 -3
- package/template/_gitignore +3 -0
- package/template/package.json +9 -0
- package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
- package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
- package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
- package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
- /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
- /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-THWGMSRH.js.map} +0 -0
|
@@ -18,21 +18,23 @@
|
|
|
18
18
|
let componentsDisabled = $derived($parentRoute === DEFAULT_COMPONENTS_PATH);
|
|
19
19
|
let colorsDisabled = $derived($parentRoute === DEFAULT_COLORS_PATH);
|
|
20
20
|
|
|
21
|
-
// Editing flow order: Tokens → Components → Sketchstyle → Color Wheel. The condensed
|
|
21
|
+
// Editing flow order: Tokens → Components → Sketchstyle → Color Wheel → Accessibility. The condensed
|
|
22
22
|
// rail cycles through these; a view is skipped while it's disabled (already
|
|
23
23
|
// on that page) so the cycle never lands on a dead view.
|
|
24
|
-
const CYCLE: readonly EditorView[] = ['tokens', 'components', 'sketch', 'colors'];
|
|
24
|
+
const CYCLE: readonly EditorView[] = ['tokens', 'components', 'sketch', 'colors', 'accessibility'];
|
|
25
25
|
const ICONS: Record<EditorView, string> = {
|
|
26
26
|
tokens: 'fa-sliders',
|
|
27
27
|
colors: 'fa-palette',
|
|
28
28
|
components: 'fa-cubes',
|
|
29
29
|
sketch: 'fa-pen-nib',
|
|
30
|
+
accessibility: 'fa-universal-access',
|
|
30
31
|
};
|
|
31
32
|
const LABELS: Record<EditorView, string> = {
|
|
32
33
|
tokens: 'Tokens',
|
|
33
34
|
colors: 'Color Wheel',
|
|
34
35
|
components: 'Components',
|
|
35
36
|
sketch: 'Sketchstyle',
|
|
37
|
+
accessibility: 'Accessibility',
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
function set(v: EditorView) {
|
|
@@ -116,6 +118,17 @@
|
|
|
116
118
|
<span class="radio" aria-hidden="true"></span>
|
|
117
119
|
<span>Color Wheel</span>
|
|
118
120
|
</button>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
role="tab"
|
|
124
|
+
class="seg-btn"
|
|
125
|
+
class:active={$editorView === 'accessibility'}
|
|
126
|
+
aria-selected={$editorView === 'accessibility'}
|
|
127
|
+
onclick={() => set('accessibility')}
|
|
128
|
+
>
|
|
129
|
+
<span class="radio" aria-hidden="true"></span>
|
|
130
|
+
<span>Accessibility</span>
|
|
131
|
+
</button>
|
|
119
132
|
</div>
|
|
120
133
|
</div>
|
|
121
134
|
{/if}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import RadialShapePad from '../component-editor/scaffolding/RadialShapePad.svelte';
|
|
19
19
|
import UISegmentedControl from './UISegmentedControl.svelte';
|
|
20
20
|
import UIPillButton from './UIPillButton.svelte';
|
|
21
|
+
import A11yPropertyMark from './a11y/A11yPropertyMark.svelte';
|
|
21
22
|
import { snapTokenToFamily } from '../core/palettes/familySwap';
|
|
22
23
|
|
|
23
24
|
interface Props {
|
|
@@ -287,7 +288,10 @@
|
|
|
287
288
|
>
|
|
288
289
|
{#if sectionLabel || hasAside}
|
|
289
290
|
<div class="editor-header editor-section-left">
|
|
290
|
-
<span class="editor-section-
|
|
291
|
+
<span class="editor-section-heading">
|
|
292
|
+
<span class="editor-section-label">{sectionLabel ?? ''}</span>
|
|
293
|
+
<A11yPropertyMark variables={[gradientSource.targetVariable]} />
|
|
294
|
+
</span>
|
|
291
295
|
{#if !isFlat}
|
|
292
296
|
<div class="stop-actions">
|
|
293
297
|
<UIPillButton
|
|
@@ -496,6 +500,10 @@
|
|
|
496
500
|
color: var(--ui-text-primary);
|
|
497
501
|
line-height: 1;
|
|
498
502
|
}
|
|
503
|
+
.editor-section-heading {
|
|
504
|
+
display: inline-flex;
|
|
505
|
+
align-items: center;
|
|
506
|
+
}
|
|
499
507
|
.editor-section-left { grid-column: 1; }
|
|
500
508
|
.editor-section-right { grid-column: 2; }
|
|
501
509
|
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
markComponentSaved,
|
|
58
58
|
} from '../core/store/editorStore';
|
|
59
59
|
import { openThemeSlug } from '../core/store/editorConfigStore';
|
|
60
|
+
import { unsavedEdits } from '../core/store/unsavedEdits';
|
|
60
61
|
import { sketchOffTheme, themeSketchSettings } from '../core/sketch/sketchStore';
|
|
61
62
|
import {
|
|
62
63
|
componentActiveRevision,
|
|
@@ -92,7 +93,6 @@
|
|
|
92
93
|
let dirtyComponentCount = $derived(
|
|
93
94
|
Object.values($componentDirty).filter(Boolean).length,
|
|
94
95
|
);
|
|
95
|
-
let unsavedEdits = $derived($colorsAndTypeDirty || dirtyComponentCount > 0 || $sketchOffTheme);
|
|
96
96
|
|
|
97
97
|
async function refreshFiles() {
|
|
98
98
|
try {
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
themeProductionState({
|
|
283
283
|
openTheme: $openThemeSlug,
|
|
284
284
|
production: $productionTheme,
|
|
285
|
-
unsaved: unsavedEdits || $liveUnsaved,
|
|
285
|
+
unsaved: $unsavedEdits || $liveUnsaved,
|
|
286
286
|
}),
|
|
287
287
|
);
|
|
288
288
|
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
async function commitWholeTheme() {
|
|
422
422
|
const row = previewRow;
|
|
423
423
|
if (!row) return;
|
|
424
|
-
if (unsavedEdits) {
|
|
424
|
+
if ($unsavedEdits) {
|
|
425
425
|
const ok = window.confirm(
|
|
426
426
|
'Loading a theme will replace the editor’s current state and discard unsaved changes. Continue?',
|
|
427
427
|
);
|
|
@@ -490,7 +490,7 @@
|
|
|
490
490
|
const picked = previewLayer ?? previewedTheme?.colorsAndType ?? null;
|
|
491
491
|
if (!row || !picked) return;
|
|
492
492
|
const { _fileName, _source, ...buffer } = picked;
|
|
493
|
-
if (unsavedEdits) {
|
|
493
|
+
if ($unsavedEdits) {
|
|
494
494
|
const ok = window.confirm(
|
|
495
495
|
'Loading colors and type will discard unsaved changes. Continue?',
|
|
496
496
|
);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { createSubscriber } from 'svelte/reactivity';
|
|
3
|
+
import { on } from 'svelte/events';
|
|
4
|
+
import { THEME_APPLIED_EVENT } from '../core/themes/themeDocumentSync';
|
|
5
|
+
|
|
6
|
+
const themeApplied = createSubscriber((update) => on(document, THEME_APPLIED_EVENT, update));
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { editorState } from '../core/store/editorStore';
|
|
11
|
+
import { getContrastPairs } from '../core/components/contrastPairRegistry';
|
|
12
|
+
import { CONTRAST_RULES } from '../core/palettes/contrast';
|
|
13
|
+
import { measurePair, type PairMeasure } from '../core/palettes/contrastPairs';
|
|
14
|
+
import { createDomResolver } from '../core/palettes/resolveColor';
|
|
15
|
+
import { formatRatio, measureState, readoutState, ruleSentence } from './contrastReadout';
|
|
16
|
+
import UIInfoPopover from './UIInfoPopover.svelte';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
component: string;
|
|
20
|
+
variable: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { component, variable }: Props = $props();
|
|
24
|
+
|
|
25
|
+
let pairs = $derived(getContrastPairs(component, variable));
|
|
26
|
+
let measures = $state<PairMeasure[]>([]);
|
|
27
|
+
let readout = $derived(measures.length > 0 ? readoutState(measures) : null);
|
|
28
|
+
|
|
29
|
+
// Measuring reads the DOM, so it waits a frame. The store renderer writes
|
|
30
|
+
// CSS variables synchronously, so by then the probe reads what the store holds.
|
|
31
|
+
$effect(() => {
|
|
32
|
+
void $editorState;
|
|
33
|
+
themeApplied();
|
|
34
|
+
const current = pairs;
|
|
35
|
+
const frame = requestAnimationFrame(() => {
|
|
36
|
+
const resolver = createDomResolver();
|
|
37
|
+
measures = current.map((pair) => measurePair(pair, resolver));
|
|
38
|
+
});
|
|
39
|
+
return () => cancelAnimationFrame(frame);
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if readout}
|
|
44
|
+
<span
|
|
45
|
+
class="contrast-readout"
|
|
46
|
+
data-contrast-state={readout.state}
|
|
47
|
+
data-contrast-ratio={readout.measure.ratio}
|
|
48
|
+
>
|
|
49
|
+
<UIInfoPopover title="Contrast" ariaLabel={readout.ariaLabel}>
|
|
50
|
+
{#snippet trigger()}
|
|
51
|
+
<span class="contrast-value {readout.state}">
|
|
52
|
+
{#if readout.state === 'fail'}
|
|
53
|
+
<i class="fa-solid fa-triangle-exclamation contrast-mark" aria-hidden="true"></i>
|
|
54
|
+
{/if}
|
|
55
|
+
{readout.label}
|
|
56
|
+
</span>
|
|
57
|
+
{/snippet}
|
|
58
|
+
<ul class="contrast-pairs">
|
|
59
|
+
{#each measures as m (m.pair)}
|
|
60
|
+
{@const rule = CONTRAST_RULES[m.pair.role]}
|
|
61
|
+
{@const pairState = measureState(m)}
|
|
62
|
+
<li class="contrast-pair">
|
|
63
|
+
{#if m.pair.context}
|
|
64
|
+
<p class="contrast-context">{m.pair.context}</p>
|
|
65
|
+
{/if}
|
|
66
|
+
<p>{ruleSentence(m)}</p>
|
|
67
|
+
<p class="contrast-measured">
|
|
68
|
+
Measured
|
|
69
|
+
<span class="contrast-value {pairState}">
|
|
70
|
+
{#if pairState === 'fail'}
|
|
71
|
+
<i class="fa-solid fa-triangle-exclamation contrast-mark" aria-hidden="true"></i>
|
|
72
|
+
{/if}
|
|
73
|
+
{formatRatio(m.ratio)}
|
|
74
|
+
</span>
|
|
75
|
+
</p>
|
|
76
|
+
<ol class="contrast-stack" aria-label="Layers behind the color, top first">
|
|
77
|
+
{#each m.pair.backdrop as layer, i (i)}
|
|
78
|
+
<li><code>{layer}</code></li>
|
|
79
|
+
{/each}
|
|
80
|
+
<li>page background</li>
|
|
81
|
+
</ol>
|
|
82
|
+
<a class="contrast-link" href={rule.url} target="_blank" rel="noopener">
|
|
83
|
+
Success criterion {rule.criterion}
|
|
84
|
+
</a>
|
|
85
|
+
</li>
|
|
86
|
+
{/each}
|
|
87
|
+
</ul>
|
|
88
|
+
</UIInfoPopover>
|
|
89
|
+
</span>
|
|
90
|
+
{/if}
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
.contrast-readout {
|
|
94
|
+
display: inline-flex;
|
|
95
|
+
flex: 0 0 auto;
|
|
96
|
+
align-items: center;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.contrast-value {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: baseline;
|
|
102
|
+
gap: var(--ui-space-2);
|
|
103
|
+
font-family: var(--ui-font-mono);
|
|
104
|
+
font-size: var(--ui-font-size-sm);
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.contrast-value.pass {
|
|
109
|
+
color: var(--ui-text-secondary);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.contrast-value.fail {
|
|
113
|
+
color: var(--ui-highlight);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.contrast-value.inactive {
|
|
117
|
+
color: var(--ui-text-tertiary);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.contrast-mark {
|
|
121
|
+
margin-right: var(--ui-space-4);
|
|
122
|
+
font-size: var(--ui-font-size-xs);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.contrast-pairs {
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
gap: var(--ui-space-12);
|
|
129
|
+
margin: 0;
|
|
130
|
+
padding: 0;
|
|
131
|
+
list-style: none;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.contrast-pair {
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
gap: var(--ui-space-6);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.contrast-pair + .contrast-pair {
|
|
141
|
+
padding-top: var(--ui-space-12);
|
|
142
|
+
border-top: 1px solid var(--ui-border-low);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* The popover's paragraph margin would double the flex gap. */
|
|
146
|
+
.contrast-pair p {
|
|
147
|
+
margin: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.contrast-context {
|
|
151
|
+
color: var(--ui-text-primary);
|
|
152
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.contrast-measured {
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: baseline;
|
|
158
|
+
gap: var(--ui-space-6);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.contrast-measured .contrast-value {
|
|
162
|
+
font-size: inherit;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.contrast-stack {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
gap: var(--ui-space-2);
|
|
169
|
+
margin: 0;
|
|
170
|
+
padding: 0;
|
|
171
|
+
list-style: none;
|
|
172
|
+
font-size: var(--ui-font-size-xs);
|
|
173
|
+
color: var(--ui-text-tertiary);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.contrast-stack code {
|
|
177
|
+
overflow-wrap: anywhere;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.contrast-link {
|
|
181
|
+
align-self: flex-start;
|
|
182
|
+
font-size: var(--ui-font-size-xs);
|
|
183
|
+
color: var(--ui-text-primary);
|
|
184
|
+
text-decoration: underline;
|
|
185
|
+
}
|
|
186
|
+
</style>
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
interface Props {
|
|
6
6
|
title: string;
|
|
7
7
|
ariaLabel?: string;
|
|
8
|
+
/** Replaces the info icon inside the button. */
|
|
9
|
+
trigger?: Snippet;
|
|
8
10
|
children?: Snippet;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
let { title, ariaLabel, children }: Props = $props();
|
|
13
|
+
let { title, ariaLabel, trigger, children }: Props = $props();
|
|
12
14
|
|
|
13
15
|
let open = $state(false);
|
|
14
16
|
let btnEl = $state<HTMLButtonElement | undefined>(undefined);
|
|
@@ -83,13 +85,18 @@
|
|
|
83
85
|
<button
|
|
84
86
|
type="button"
|
|
85
87
|
class="info-btn"
|
|
88
|
+
class:has-trigger={!!trigger}
|
|
86
89
|
aria-label={ariaLabel ?? `About ${title}`}
|
|
87
90
|
aria-expanded={open}
|
|
88
91
|
bind:this={btnEl}
|
|
89
92
|
data-info-popover-root
|
|
90
93
|
onclick={() => (open = !open)}
|
|
91
94
|
>
|
|
92
|
-
|
|
95
|
+
{#if trigger}
|
|
96
|
+
{@render trigger()}
|
|
97
|
+
{:else}
|
|
98
|
+
<i class="fas fa-circle-info"></i>
|
|
99
|
+
{/if}
|
|
93
100
|
</button>
|
|
94
101
|
|
|
95
102
|
{#if open}
|
|
@@ -140,6 +147,13 @@
|
|
|
140
147
|
color: var(--ui-text-primary);
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
.info-btn.has-trigger {
|
|
151
|
+
width: auto;
|
|
152
|
+
height: auto;
|
|
153
|
+
color: inherit;
|
|
154
|
+
font: inherit;
|
|
155
|
+
}
|
|
156
|
+
|
|
143
157
|
/* Fixed positioning escapes any parent overflow/stacking context. JS
|
|
144
158
|
anchors it to the right of the trigger; falls back to centered-below
|
|
145
159
|
then flips up near the viewport bottom. */
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
import UITokenSelector from './UITokenSelector.svelte';
|
|
26
26
|
import ColorEditPanel from './ColorEditPanel.svelte';
|
|
27
27
|
import CustomColorReplaceStep from './CustomColorReplaceStep.svelte';
|
|
28
|
+
import UIContrastReadout from './UIContrastReadout.svelte';
|
|
29
|
+
import { getContrastPairs } from '../core/components/contrastPairRegistry';
|
|
28
30
|
|
|
29
31
|
/** Honor prefers-reduced-motion: `t()` zeroes durations when the OS asks for
|
|
30
32
|
* less motion, so the detail-row enter/exit slides skip outright. Mirrors
|
|
@@ -89,6 +91,8 @@
|
|
|
89
91
|
replacing = null,
|
|
90
92
|
}: Props = $props();
|
|
91
93
|
|
|
94
|
+
let hasContrastPairs = $derived(!!component && getContrastPairs(component, variable).length > 0);
|
|
95
|
+
|
|
92
96
|
const clampOpacity = (n: number) => Math.max(minOpacity, Math.min(100, Math.round(n)));
|
|
93
97
|
let noneAllowed = $derived(showNone && minOpacity <= 0);
|
|
94
98
|
|
|
@@ -740,6 +744,10 @@
|
|
|
740
744
|
});
|
|
741
745
|
</script>
|
|
742
746
|
|
|
747
|
+
{#snippet contrastReadout()}
|
|
748
|
+
{#if component}<UIContrastReadout {component} {variable} />{/if}
|
|
749
|
+
{/snippet}
|
|
750
|
+
|
|
743
751
|
<UITokenSelector
|
|
744
752
|
bind:this={selector}
|
|
745
753
|
{variable}
|
|
@@ -748,6 +756,7 @@
|
|
|
748
756
|
{disabled}
|
|
749
757
|
{selectionsLocked}
|
|
750
758
|
{onwrite}
|
|
759
|
+
metaAfter={hasContrastPairs ? contrastReadout : undefined}
|
|
751
760
|
dropdownMinWidth={session ? '22rem' : '14rem'}
|
|
752
761
|
dropdownMaxWidth="calc(100vw - 2rem)"
|
|
753
762
|
hideDefaultHeader={!!selectedFamily}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import UILinkToggle from './UILinkToggle.svelte';
|
|
24
24
|
import UIRelinkConfirmDialog from './UIRelinkConfirmDialog.svelte';
|
|
25
25
|
import { anchorToTrigger } from './anchorToTrigger';
|
|
26
|
+
import A11yPropertyMark from './a11y/A11yPropertyMark.svelte';
|
|
26
27
|
|
|
27
28
|
type DropdownContext = { close: () => void; handleReset: () => void };
|
|
28
29
|
|
|
@@ -53,6 +54,8 @@
|
|
|
53
54
|
triggerTitle?: Snippet;
|
|
54
55
|
/** Optional meta text rendered after the trigger. Renamed from `slot="trigger-meta"` in 0.5.0. */
|
|
55
56
|
triggerMeta?: Snippet;
|
|
57
|
+
/** Optional content after the meta text, in the same column. The meta text truncates first. */
|
|
58
|
+
metaAfter?: Snippet;
|
|
56
59
|
/** Optional dropdown header replacement. */
|
|
57
60
|
header?: Snippet;
|
|
58
61
|
/** Optional dropdown content above the selections. */
|
|
@@ -87,6 +90,7 @@
|
|
|
87
90
|
triggerText,
|
|
88
91
|
triggerTitle,
|
|
89
92
|
triggerMeta,
|
|
93
|
+
metaAfter,
|
|
90
94
|
header,
|
|
91
95
|
subheader,
|
|
92
96
|
children,
|
|
@@ -292,6 +296,7 @@
|
|
|
292
296
|
bind:this={container}
|
|
293
297
|
>
|
|
294
298
|
<div class="ui-ts-trigger-wrap">
|
|
299
|
+
<A11yPropertyMark variables={[variable]} placement="gutter" />
|
|
295
300
|
<button
|
|
296
301
|
bind:this={triggerEl}
|
|
297
302
|
class="ui-ts-trigger"
|
|
@@ -362,11 +367,20 @@
|
|
|
362
367
|
{/if}
|
|
363
368
|
</div>
|
|
364
369
|
|
|
365
|
-
{#
|
|
370
|
+
{#snippet metaText(meta: Snippet)}
|
|
366
371
|
<span
|
|
367
372
|
class="ui-ts-meta-text"
|
|
368
373
|
use:mirrorTextToTitle
|
|
369
|
-
>{@render
|
|
374
|
+
>{@render meta()}</span>
|
|
375
|
+
{/snippet}
|
|
376
|
+
|
|
377
|
+
{#if metaAfter}
|
|
378
|
+
<div class="ui-ts-meta">
|
|
379
|
+
{#if triggerMeta}{@render metaText(triggerMeta)}{/if}
|
|
380
|
+
{@render metaAfter()}
|
|
381
|
+
</div>
|
|
382
|
+
{:else if triggerMeta}
|
|
383
|
+
{@render metaText(triggerMeta)}
|
|
370
384
|
{/if}
|
|
371
385
|
</div>
|
|
372
386
|
|
|
@@ -518,6 +532,16 @@
|
|
|
518
532
|
min-width: 0;
|
|
519
533
|
}
|
|
520
534
|
|
|
535
|
+
/* Column 3 when `metaAfter` is set. `min-width: 0` keeps the pair from
|
|
536
|
+
widening the column; the meta text gives up width first. */
|
|
537
|
+
.ui-ts-meta {
|
|
538
|
+
display: flex;
|
|
539
|
+
align-items: center;
|
|
540
|
+
gap: var(--ui-space-6);
|
|
541
|
+
align-self: center;
|
|
542
|
+
min-width: 0;
|
|
543
|
+
}
|
|
544
|
+
|
|
521
545
|
.ui-ts-chevron {
|
|
522
546
|
font-size: 0.625rem;
|
|
523
547
|
color: var(--ui-text-secondary);
|