@astryxdesign/core 0.1.4-canary.37c19d3 → 0.1.4-canary.39ffd21
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/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +1 -8
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +11 -31
- package/dist/Calendar/styles.d.ts +2 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +2 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +0 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +1 -8
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +1 -3
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -26
- package/dist/CodeBlock/CodeBlock.d.ts +1 -10
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +5 -23
- package/dist/Collapsible/Collapsible.d.ts +3 -9
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +13 -101
- package/dist/Collapsible/CollapsibleGroup.d.ts +7 -39
- package/dist/Collapsible/CollapsibleGroup.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroup.js +9 -70
- package/dist/Collapsible/CollapsibleGroupContext.d.ts +0 -31
- package/dist/Collapsible/CollapsibleGroupContext.d.ts.map +1 -1
- package/dist/Collapsible/CollapsibleGroupContext.js +3 -33
- package/dist/Collapsible/index.d.ts +0 -1
- package/dist/Collapsible/index.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +0 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +0 -2
- package/dist/Grid/Grid.d.ts +3 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +9 -26
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +1 -5
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +2 -26
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +16 -32
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +36 -87
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +21 -39
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +1 -2
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +2 -10
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +2 -6
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +7 -34
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +2 -12
- package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
- package/dist/Table/plugins/columnResize/useTableColumnResize.js +2 -5
- package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
- package/dist/Table/plugins/pagination/paginateData.js +1 -5
- package/dist/Table/plugins/selection/useTableSelectionState.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelectionState.js +1 -11
- package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortableState.js +4 -8
- package/dist/Thumbnail/Thumbnail.d.ts +2 -3
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +0 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +1 -6
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +3 -11
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +0 -12
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +2 -5
- package/dist/astryx.css +0 -7
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/focusableSelector.d.ts +1 -1
- package/dist/hooks/focusableSelector.d.ts.map +1 -1
- package/dist/hooks/focusableSelector.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -3
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -38
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +3 -10
- package/dist/theme/hct.d.ts +15 -0
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +9 -9
- package/dist/theme/tokens.d.ts +2 -10
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +4 -220
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -2
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +5 -8
- package/package.json +1 -1
- package/src/Avatar/Avatar.doc.mjs +0 -11
- package/src/Banner/Banner.doc.mjs +0 -8
- package/src/Banner/Banner.test.tsx +0 -38
- package/src/Banner/Banner.tsx +1 -8
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Calendar/Calendar.test.tsx +4 -112
- package/src/Calendar/Calendar.tsx +5 -26
- package/src/Calendar/styles.ts +2 -12
- package/src/Carousel/Carousel.doc.mjs +12 -12
- package/src/Carousel/Carousel.test.tsx +0 -13
- package/src/Carousel/Carousel.tsx +0 -1
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatToolCalls.test.tsx +0 -55
- package/src/Chat/ChatToolCalls.tsx +12 -10
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -3
- package/src/CheckboxInput/CheckboxInput.test.tsx +4 -71
- package/src/CheckboxInput/CheckboxInput.tsx +0 -2
- package/src/Citation/Citation.doc.mjs +0 -5
- package/src/ClickableCard/ClickableCard.tsx +2 -45
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -7
- package/src/CodeBlock/CodeBlock.test.tsx +3 -87
- package/src/CodeBlock/CodeBlock.tsx +3 -43
- package/src/Collapsible/Collapsible.doc.mjs +4 -8
- package/src/Collapsible/Collapsible.tsx +10 -86
- package/src/Collapsible/CollapsibleGroup.doc.mjs +3 -27
- package/src/Collapsible/CollapsibleGroup.test.tsx +0 -288
- package/src/Collapsible/CollapsibleGroup.tsx +9 -107
- package/src/Collapsible/CollapsibleGroupContext.tsx +2 -42
- package/src/Collapsible/index.ts +0 -4
- package/src/CommandPalette/CommandPaletteEmpty.doc.mjs +0 -5
- package/src/CommandPalette/CommandPaletteGroup.doc.mjs +0 -10
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +0 -6
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +0 -6
- package/src/CommandPalette/CommandPaletteList.doc.mjs +0 -9
- package/src/ContextMenu/ContextMenu.doc.mjs +0 -15
- package/src/ContextMenu/ContextMenu.test.tsx +0 -15
- package/src/ContextMenu/ContextMenu.tsx +0 -2
- package/src/ContextMenu/ContextMenuItem.doc.mjs +0 -6
- package/src/DateTimeInput/DateTimeInput.test.tsx +0 -70
- package/src/DateTimeInput/DateTimeInput.tsx +0 -2
- package/src/Dialog/DialogHeader.doc.mjs +0 -45
- package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -1
- package/src/Field/FieldLabel.test.tsx +2 -2
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -0
- package/src/Grid/Grid.test.tsx +18 -43
- package/src/Grid/Grid.tsx +9 -26
- package/src/Icon/Icon.test.tsx +29 -41
- package/src/Icon/Icon.tsx +2 -6
- package/src/Layout/LayoutContent.doc.mjs +0 -12
- package/src/Layout/LayoutPanel.doc.mjs +0 -40
- package/src/Lightbox/Lightbox.doc.mjs +0 -5
- package/src/Lightbox/Lightbox.test.tsx +2 -123
- package/src/Lightbox/Lightbox.tsx +1 -29
- package/src/NavMenu/NavMenu.doc.mjs +0 -15
- package/src/NumberInput/NumberInput.test.tsx +3 -48
- package/src/NumberInput/NumberInput.tsx +15 -32
- package/src/Overlay/Overlay.doc.mjs +0 -6
- package/src/Pagination/Pagination.test.tsx +0 -143
- package/src/Pagination/Pagination.tsx +23 -75
- package/src/Popover/Popover.test.tsx +0 -65
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.test.tsx +1 -109
- package/src/PowerSearch/PowerSearch.tsx +16 -37
- package/src/ProgressBar/ProgressBar.test.tsx +0 -10
- package/src/ProgressBar/ProgressBar.tsx +1 -2
- package/src/Resizable/ResizeHandle.tsx +2 -10
- package/src/SegmentedControl/SegmentedControl.doc.mjs +1 -1
- package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
- package/src/SideNav/SideNav.doc.mjs +1 -7
- package/src/SideNav/SideNav.test.tsx +0 -20
- package/src/SideNav/SideNav.tsx +2 -6
- package/src/TabList/TabList.test.tsx +1 -185
- package/src/TabList/TabMenu.tsx +9 -45
- package/src/Table/BaseTable.tsx +1 -11
- package/src/Table/Table.test.tsx +0 -51
- package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -13
- package/src/Table/plugins/columnResize/useTableColumnResize.tsx +1 -3
- package/src/Table/plugins/pagination/paginateData.ts +1 -5
- package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -13
- package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -23
- package/src/Table/plugins/selection/useTableSelectionState.tsx +6 -11
- package/src/Table/plugins/sortable/useTableSortableState.test.tsx +1 -35
- package/src/Table/plugins/sortable/useTableSortableState.tsx +4 -8
- package/src/TextArea/TextArea.test.tsx +3 -3
- package/src/TextInput/TextInput.test.tsx +3 -3
- package/src/Thumbnail/Thumbnail.test.tsx +2 -36
- package/src/Thumbnail/Thumbnail.tsx +2 -4
- package/src/TimeInput/TimeInput.test.tsx +0 -37
- package/src/TimeInput/TimeInput.tsx +1 -11
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Toast/Toast.tsx +3 -11
- package/src/Toast/ToastViewport.test.tsx +0 -64
- package/src/Toast/ToastViewport.tsx +0 -12
- package/src/ToggleButton/ToggleButton.doc.mjs +0 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/TopNav/TopNav.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/Typeahead/BaseTypeahead.doc.mjs +0 -19
- package/src/Typeahead/BaseTypeahead.tsx +2 -5
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.test.tsx +1 -45
- package/src/hooks/focusableSelector.ts +1 -1
- package/src/hooks/useFocusTrap.doc.mjs +2 -4
- package/src/hooks/useFocusTrap.test.tsx +1 -149
- package/src/hooks/useFocusTrap.ts +1 -50
- package/src/theme/SyntaxTheme.doc.mjs +4 -4
- package/src/theme/defineTheme.ts +13 -4
- package/src/theme/derivedVarRegistry.test.ts +2 -2
- package/src/theme/expandColorScale.test.ts +0 -29
- package/src/theme/expandColorScale.ts +6 -11
- package/src/theme/hct.ts +17 -9
- package/src/theme/tokens.test.ts +9 -182
- package/src/theme/tokens.ts +4 -239
- package/src/theme/useTheme.test.tsx +0 -20
- package/src/utils/index.ts +0 -3
- package/src/utils/timeParser.test.ts +0 -13
- package/src/utils/timeParser.ts +5 -8
- package/dist/utils/color.d.ts +0 -42
- package/dist/utils/color.d.ts.map +0 -1
- package/dist/utils/color.js +0 -150
- package/src/Resizable/ResizeHandle.test.tsx +0 -180
- package/src/__tests__/TestIcon.tsx +0 -26
- package/src/docPropReferences.test.ts +0 -178
- package/src/utils/color.test.ts +0 -133
- package/src/utils/color.ts +0 -148
package/dist/utils/color.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file color.ts
|
|
5
|
-
* @input CSS color strings (hex, rgb()/rgba(), a small set of named colors)
|
|
6
|
-
* @output Shared color parsing/formatting primitives used across the design system
|
|
7
|
-
* @position Package utility; backs theme token resolution, HCT color math, and chart rendering
|
|
8
|
-
*
|
|
9
|
-
* A single home for the color parsers that were previously duplicated across
|
|
10
|
-
* the theme layer, charts, and lab. Consumers get one well-tested definition of
|
|
11
|
-
* "parse this color" rather than several subtly different regexes.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/** A color decomposed into 0-255 RGB channels and a 0-1 alpha. */
|
|
15
|
-
|
|
16
|
-
/** The named colors the design system relies on in token expressions. */
|
|
17
|
-
const NAMED_COLORS = {
|
|
18
|
-
transparent: {
|
|
19
|
-
r: 0,
|
|
20
|
-
g: 0,
|
|
21
|
-
b: 0,
|
|
22
|
-
a: 0
|
|
23
|
-
},
|
|
24
|
-
black: {
|
|
25
|
-
r: 0,
|
|
26
|
-
g: 0,
|
|
27
|
-
b: 0,
|
|
28
|
-
a: 1
|
|
29
|
-
},
|
|
30
|
-
white: {
|
|
31
|
-
r: 255,
|
|
32
|
-
g: 255,
|
|
33
|
-
b: 255,
|
|
34
|
-
a: 1
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
38
|
-
|
|
39
|
-
/** Expand a shorthand hex body (`rgb`/`rgba`) to its full form (`rrggbb`/`rrggbbaa`). */
|
|
40
|
-
function expandShorthand(body) {
|
|
41
|
-
return body.split('').map(c => c + c).join('');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Parse a hex color into {@link RGBA}. Accepts `#rgb`, `#rgba`, `#rrggbb`, and
|
|
46
|
-
* `#rrggbbaa`, with or without the leading `#`. Returns null for anything else.
|
|
47
|
-
*/
|
|
48
|
-
export function parseHex(hex) {
|
|
49
|
-
if (typeof hex !== 'string') {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const body = hex.trim().replace(/^#/, '');
|
|
53
|
-
const normalized = body.length === 3 || body.length === 4 ? expandShorthand(body) : body;
|
|
54
|
-
if (normalized.length !== 6 && normalized.length !== 8 || !/^[0-9a-fA-F]+$/.test(normalized)) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
r: parseInt(normalized.slice(0, 2), 16),
|
|
59
|
-
g: parseInt(normalized.slice(2, 4), 16),
|
|
60
|
-
b: parseInt(normalized.slice(4, 6), 16),
|
|
61
|
-
a: normalized.length === 8 ? parseInt(normalized.slice(6, 8), 16) / 255 : 1
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Parse an `rgb()`/`rgba()` color into {@link RGBA}. Accepts comma- or
|
|
67
|
-
* space-separated channels, an optional `/ alpha`, and percentage channels.
|
|
68
|
-
*/
|
|
69
|
-
export function parseRgb(value) {
|
|
70
|
-
const open = value.indexOf('(');
|
|
71
|
-
if (open === -1 || !value.trim().endsWith(')')) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
const body = value.slice(open + 1, value.lastIndexOf(')')).replace(/\//g, ' ');
|
|
75
|
-
const parts = body.split(/[\s,]+/).map(p => p.trim()).filter(Boolean);
|
|
76
|
-
if (parts.length < 3) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
const channel = p => {
|
|
80
|
-
const n = p.endsWith('%') ? parseFloat(p) / 100 * 255 : parseFloat(p);
|
|
81
|
-
return clamp(n, 0, 255);
|
|
82
|
-
};
|
|
83
|
-
const r = channel(parts[0]);
|
|
84
|
-
const g = channel(parts[1]);
|
|
85
|
-
const b = channel(parts[2]);
|
|
86
|
-
if ([r, g, b].some(Number.isNaN)) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
let a = 1;
|
|
90
|
-
if (parts.length >= 4) {
|
|
91
|
-
const raw = parts[3];
|
|
92
|
-
a = raw.endsWith('%') ? parseFloat(raw) / 100 : parseFloat(raw);
|
|
93
|
-
if (Number.isNaN(a)) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
a = clamp(a, 0, 1);
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
r,
|
|
100
|
-
g,
|
|
101
|
-
b,
|
|
102
|
-
a
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Parse a concrete CSS color string into {@link RGBA}. Supports hex,
|
|
108
|
-
* `rgb()`/`rgba()`, and the named colors used in token expressions. Returns
|
|
109
|
-
* null for anything it can't evaluate (e.g. `var()`, `oklch()`, unknown names)
|
|
110
|
-
* so callers can preserve the original expression rather than guessing.
|
|
111
|
-
*/
|
|
112
|
-
export function parseColor(value) {
|
|
113
|
-
const trimmed = value.trim();
|
|
114
|
-
const named = NAMED_COLORS[trimmed.toLowerCase()];
|
|
115
|
-
if (named) {
|
|
116
|
-
return {
|
|
117
|
-
...named
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
if (trimmed.startsWith('#')) {
|
|
121
|
-
return parseHex(trimmed);
|
|
122
|
-
}
|
|
123
|
-
if (/^rgba?\(/i.test(trimmed)) {
|
|
124
|
-
return parseRgb(trimmed);
|
|
125
|
-
}
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Format RGB channels (0-255) as an uppercase `#RRGGBB` string. */
|
|
130
|
-
export function formatHex(r, g, b) {
|
|
131
|
-
const channel = c => clamp(Math.round(c), 0, 255).toString(16).padStart(2, '0').toUpperCase();
|
|
132
|
-
return `#${channel(r)}${channel(g)}${channel(b)}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Serialize {@link RGBA} to a compact, JS-usable CSS color: `#RRGGBB` when
|
|
137
|
-
* fully opaque, otherwise `rgba(r, g, b, a)`.
|
|
138
|
-
*/
|
|
139
|
-
export function formatColor({
|
|
140
|
-
r,
|
|
141
|
-
g,
|
|
142
|
-
b,
|
|
143
|
-
a
|
|
144
|
-
}) {
|
|
145
|
-
if (a >= 1) {
|
|
146
|
-
return formatHex(r, g, b);
|
|
147
|
-
}
|
|
148
|
-
const round = n => clamp(Math.round(n), 0, 255);
|
|
149
|
-
return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${parseFloat(a.toFixed(4))})`;
|
|
150
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file ResizeHandle.test.tsx
|
|
5
|
-
* @input Uses vitest, @testing-library/react, useResizable, ResizeHandle
|
|
6
|
-
* @output Unit tests for ResizeHandle keyboard operability and ARIA state
|
|
7
|
-
* @position Testing; validates ResizeHandle.tsx implementation
|
|
8
|
-
*
|
|
9
|
-
* SYNC: When ResizeHandle.tsx changes, update tests to match new behavior
|
|
10
|
-
*
|
|
11
|
-
* These tests guard the WAI-ARIA window-splitter keyboard contract: the
|
|
12
|
-
* keydown handler must live on the focusable `role="separator"` element.
|
|
13
|
-
* Keyboard resizing is pure state math in useResizable (no layout
|
|
14
|
-
* measurement), so the observable effect asserted here is aria-valuenow,
|
|
15
|
-
* which is bound to the region's `_size`.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import {describe, it, expect, vi} from 'vitest';
|
|
19
|
-
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
20
|
-
import {ResizeHandle} from './ResizeHandle';
|
|
21
|
-
import type {ResizeHandleProps} from './ResizeHandle';
|
|
22
|
-
import {useResizable} from './useResizable';
|
|
23
|
-
import type {UseResizableSingleConfig} from './useResizable';
|
|
24
|
-
|
|
25
|
-
const KEYBOARD_STEP = 10;
|
|
26
|
-
const KEYBOARD_LARGE_STEP = 50;
|
|
27
|
-
|
|
28
|
-
function Harness({
|
|
29
|
-
config,
|
|
30
|
-
handleProps,
|
|
31
|
-
}: {
|
|
32
|
-
config?: UseResizableSingleConfig;
|
|
33
|
-
handleProps?: Partial<ResizeHandleProps>;
|
|
34
|
-
}) {
|
|
35
|
-
const region = useResizable(
|
|
36
|
-
config ?? {defaultSize: 200, minSizePx: 100, maxSizePx: 400},
|
|
37
|
-
);
|
|
38
|
-
return (
|
|
39
|
-
<ResizeHandle resizable={region.props} label="Resize" {...handleProps} />
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getSeparator(): HTMLElement {
|
|
44
|
-
return screen.getByRole('separator');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe('ResizeHandle', () => {
|
|
48
|
-
// --- ARIA wiring ---
|
|
49
|
-
|
|
50
|
-
it('exposes the region size and bounds via ARIA', () => {
|
|
51
|
-
render(<Harness />);
|
|
52
|
-
const separator = getSeparator();
|
|
53
|
-
expect(separator).toHaveAttribute('aria-valuenow', '200');
|
|
54
|
-
expect(separator).toHaveAttribute('aria-valuemin', '100');
|
|
55
|
-
expect(separator).toHaveAttribute('aria-valuemax', '400');
|
|
56
|
-
// Horizontal layout splits along the vertical axis.
|
|
57
|
-
expect(separator).toHaveAttribute('aria-orientation', 'vertical');
|
|
58
|
-
expect(separator).toHaveAttribute('aria-label', 'Resize');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('makes the separator focusable', () => {
|
|
62
|
-
render(<Harness />);
|
|
63
|
-
const separator = getSeparator();
|
|
64
|
-
expect(separator).toHaveAttribute('tabindex', '0');
|
|
65
|
-
act(() => separator.focus());
|
|
66
|
-
expect(separator).toHaveFocus();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// --- Keyboard resizing (the fix: handler lives on the focused separator) ---
|
|
70
|
-
|
|
71
|
-
it('grows the panel by a step on ArrowRight', () => {
|
|
72
|
-
render(<Harness />);
|
|
73
|
-
const separator = getSeparator();
|
|
74
|
-
act(() => separator.focus());
|
|
75
|
-
fireEvent.keyDown(separator, {key: 'ArrowRight'});
|
|
76
|
-
expect(separator).toHaveAttribute(
|
|
77
|
-
'aria-valuenow',
|
|
78
|
-
String(200 + KEYBOARD_STEP),
|
|
79
|
-
);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('shrinks the panel by a step on ArrowLeft', () => {
|
|
83
|
-
render(<Harness />);
|
|
84
|
-
const separator = getSeparator();
|
|
85
|
-
act(() => separator.focus());
|
|
86
|
-
fireEvent.keyDown(separator, {key: 'ArrowLeft'});
|
|
87
|
-
expect(separator).toHaveAttribute(
|
|
88
|
-
'aria-valuenow',
|
|
89
|
-
String(200 - KEYBOARD_STEP),
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('uses the large step when Shift is held', () => {
|
|
94
|
-
render(<Harness />);
|
|
95
|
-
const separator = getSeparator();
|
|
96
|
-
act(() => separator.focus());
|
|
97
|
-
fireEvent.keyDown(separator, {key: 'ArrowRight', shiftKey: true});
|
|
98
|
-
expect(separator).toHaveAttribute(
|
|
99
|
-
'aria-valuenow',
|
|
100
|
-
String(200 + KEYBOARD_LARGE_STEP),
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('jumps to the minimum on Home', () => {
|
|
105
|
-
render(<Harness />);
|
|
106
|
-
const separator = getSeparator();
|
|
107
|
-
act(() => separator.focus());
|
|
108
|
-
fireEvent.keyDown(separator, {key: 'Home'});
|
|
109
|
-
expect(separator).toHaveAttribute('aria-valuenow', '100');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('jumps to the maximum on End', () => {
|
|
113
|
-
render(<Harness />);
|
|
114
|
-
const separator = getSeparator();
|
|
115
|
-
act(() => separator.focus());
|
|
116
|
-
fireEvent.keyDown(separator, {key: 'End'});
|
|
117
|
-
expect(separator).toHaveAttribute('aria-valuenow', '400');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('resizes along the block axis for a vertical handle', () => {
|
|
121
|
-
render(
|
|
122
|
-
<Harness
|
|
123
|
-
config={{defaultSize: 200, minSizePx: 100, maxSizePx: 400}}
|
|
124
|
-
handleProps={{direction: 'vertical'}}
|
|
125
|
-
/>,
|
|
126
|
-
);
|
|
127
|
-
const separator = getSeparator();
|
|
128
|
-
expect(separator).toHaveAttribute('aria-orientation', 'horizontal');
|
|
129
|
-
act(() => separator.focus());
|
|
130
|
-
fireEvent.keyDown(separator, {key: 'ArrowDown'});
|
|
131
|
-
expect(separator).toHaveAttribute(
|
|
132
|
-
'aria-valuenow',
|
|
133
|
-
String(200 + KEYBOARD_STEP),
|
|
134
|
-
);
|
|
135
|
-
fireEvent.keyDown(separator, {key: 'ArrowUp'});
|
|
136
|
-
expect(separator).toHaveAttribute('aria-valuenow', '200');
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('collapses on Enter when the region is collapsible', () => {
|
|
140
|
-
render(
|
|
141
|
-
<Harness
|
|
142
|
-
config={{
|
|
143
|
-
defaultSize: 200,
|
|
144
|
-
minSizePx: 100,
|
|
145
|
-
maxSizePx: 400,
|
|
146
|
-
collapsible: true,
|
|
147
|
-
}}
|
|
148
|
-
/>,
|
|
149
|
-
);
|
|
150
|
-
const separator = getSeparator();
|
|
151
|
-
act(() => separator.focus());
|
|
152
|
-
fireEvent.keyDown(separator, {key: 'Enter'});
|
|
153
|
-
expect(separator).toHaveAttribute('aria-valuenow', '0');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// --- Disabled guard ---
|
|
157
|
-
|
|
158
|
-
it('ignores keyboard input when disabled', () => {
|
|
159
|
-
render(<Harness handleProps={{isDisabled: true}} />);
|
|
160
|
-
const separator = getSeparator();
|
|
161
|
-
expect(separator).toHaveAttribute('tabindex', '-1');
|
|
162
|
-
fireEvent.keyDown(separator, {key: 'ArrowRight'});
|
|
163
|
-
expect(separator).toHaveAttribute('aria-valuenow', '200');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// --- Prop composition (ordering choice: handler sits after {...props}) ---
|
|
167
|
-
|
|
168
|
-
it('runs a consumer onKeyDown alongside keyboard resizing', () => {
|
|
169
|
-
const onKeyDown = vi.fn();
|
|
170
|
-
render(<Harness handleProps={{onKeyDown}} />);
|
|
171
|
-
const separator = getSeparator();
|
|
172
|
-
act(() => separator.focus());
|
|
173
|
-
fireEvent.keyDown(separator, {key: 'ArrowRight'});
|
|
174
|
-
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
|
175
|
-
expect(separator).toHaveAttribute(
|
|
176
|
-
'aria-valuenow',
|
|
177
|
-
String(200 + KEYBOARD_STEP),
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file TestIcon.tsx
|
|
5
|
-
* @input Standard SVG props
|
|
6
|
-
* @output A minimal SVG React component for tests
|
|
7
|
-
* @position Test fixture; a stand-in external SVG icon for Icon component-mode tests
|
|
8
|
-
*
|
|
9
|
-
* A trivial SVG component used by tests that need to pass a custom SVG icon to
|
|
10
|
-
* `Icon` (component mode) or component `*Icon` props. It forwards all standard
|
|
11
|
-
* SVG props and its ref so tests exercising prop/ref forwarding behave the same
|
|
12
|
-
* as a real third-party icon component.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import React, {type SVGProps} from 'react';
|
|
16
|
-
|
|
17
|
-
export function TestIcon({
|
|
18
|
-
ref,
|
|
19
|
-
...props
|
|
20
|
-
}: SVGProps<SVGSVGElement> & {ref?: React.Ref<SVGSVGElement>}) {
|
|
21
|
-
return (
|
|
22
|
-
<svg ref={ref} viewBox="0 0 24 24" {...props}>
|
|
23
|
-
<path d="M4 4h16v16H4z" />
|
|
24
|
-
</svg>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
|
-
/**
|
|
5
|
-
* @file Guards doc prose against docs/source prop mismatches (#3360).
|
|
6
|
-
*
|
|
7
|
-
* Scans every {Name}.doc.mjs for prose phrases like "the foo prop on Bar"
|
|
8
|
-
* and verifies the referenced prop is documented on the referenced
|
|
9
|
-
* component. Doc prose is LLM training signal: a reference to a prop that
|
|
10
|
-
* doesn't exist steers codegen toward hallucinated APIs (#3360 shipped a
|
|
11
|
-
* bullet pointing at a nonexistent CodeBlock syntaxTheme prop).
|
|
12
|
-
*
|
|
13
|
-
* A reference to a missing prop is allowed only when the same string
|
|
14
|
-
* explicitly negates it ("no such prop exists" / "does not exist"), the
|
|
15
|
-
* corrective-bullet pattern used to counter known hallucinations.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import {describe, it, expect} from 'vitest';
|
|
19
|
-
import {readdirSync} from 'node:fs';
|
|
20
|
-
import {join, relative} from 'node:path';
|
|
21
|
-
|
|
22
|
-
const SRC_DIR = __dirname;
|
|
23
|
-
|
|
24
|
-
type DocModule = {
|
|
25
|
-
docs?: {
|
|
26
|
-
name?: string;
|
|
27
|
-
props?: {name: string}[];
|
|
28
|
-
components?: {name?: string; props?: {name: string}[]}[];
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Props intentionally omitted from doc props[]: PropDoc in docs-types.ts
|
|
34
|
-
* says to skip internal/styling props, so prose may reference them even
|
|
35
|
-
* though no doc lists them.
|
|
36
|
-
*/
|
|
37
|
-
const UNIVERSAL_PROPS = new Set([
|
|
38
|
-
'xstyle',
|
|
39
|
-
'className',
|
|
40
|
-
'style',
|
|
41
|
-
'ref',
|
|
42
|
-
'data-testid',
|
|
43
|
-
]);
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Matches prose like "the foo prop on Bar", "foo prop of Bar", and
|
|
47
|
-
* conjunction targets: "foo prop on Bar or Baz". Group 1 is the prop
|
|
48
|
-
* (camelCase, lowercase first letter), group 2 the component name list.
|
|
49
|
-
*/
|
|
50
|
-
const PROP_REF_PATTERN =
|
|
51
|
-
/\b([a-z][A-Za-z0-9]*) prop (?:on|of) (?:the )?([A-Z][A-Za-z0-9]*(?:(?:,(?: and| or)?| and| or) [A-Z][A-Za-z0-9]*)*)/g;
|
|
52
|
-
|
|
53
|
-
/** A same-string negation marks the reference as a deliberate counter-signal. */
|
|
54
|
-
const NEGATION_PATTERN = /no such prop|does not exist|doesn't exist/i;
|
|
55
|
-
|
|
56
|
-
function findDocFiles(dir: string): string[] {
|
|
57
|
-
const files: string[] = [];
|
|
58
|
-
for (const entry of readdirSync(dir, {withFileTypes: true})) {
|
|
59
|
-
const full = join(dir, entry.name);
|
|
60
|
-
if (entry.isDirectory()) {
|
|
61
|
-
files.push(...findDocFiles(full));
|
|
62
|
-
} else if (entry.name.endsWith('.doc.mjs')) {
|
|
63
|
-
files.push(full);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return files;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Collect every string value reachable from a doc module export. */
|
|
70
|
-
function collectStrings(value: unknown, out: string[]): void {
|
|
71
|
-
if (typeof value === 'string') {
|
|
72
|
-
out.push(value);
|
|
73
|
-
} else if (Array.isArray(value)) {
|
|
74
|
-
for (const item of value) {
|
|
75
|
-
collectStrings(item, out);
|
|
76
|
-
}
|
|
77
|
-
} else if (value && typeof value === 'object') {
|
|
78
|
-
for (const item of Object.values(value)) {
|
|
79
|
-
collectStrings(item, out);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const docFiles = findDocFiles(SRC_DIR);
|
|
85
|
-
|
|
86
|
-
// Component name → documented prop names, from every doc file's props[]
|
|
87
|
-
// (single and sub-component docs) and inline components[] entries.
|
|
88
|
-
const documentedProps = new Map<string, Set<string>>();
|
|
89
|
-
for (const file of docFiles) {
|
|
90
|
-
const mod = require(file) as DocModule;
|
|
91
|
-
const docs = mod.docs;
|
|
92
|
-
if (!docs) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const entries = [
|
|
96
|
-
{name: docs.name, props: docs.props},
|
|
97
|
-
...(docs.components || []),
|
|
98
|
-
];
|
|
99
|
-
for (const {name, props} of entries) {
|
|
100
|
-
if (!name || !Array.isArray(props)) {
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const set = documentedProps.get(name) || new Set<string>();
|
|
104
|
-
for (const prop of props) {
|
|
105
|
-
set.add(prop.name);
|
|
106
|
-
}
|
|
107
|
-
documentedProps.set(name, set);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
interface Violation {
|
|
112
|
-
file: string;
|
|
113
|
-
component: string;
|
|
114
|
-
prop: string;
|
|
115
|
-
excerpt: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function findViolations(): Violation[] {
|
|
119
|
-
const violations: Violation[] = [];
|
|
120
|
-
for (const file of docFiles) {
|
|
121
|
-
const strings: string[] = [];
|
|
122
|
-
collectStrings(require(file), strings);
|
|
123
|
-
for (const text of strings) {
|
|
124
|
-
let match;
|
|
125
|
-
while ((match = PROP_REF_PATTERN.exec(text)) !== null) {
|
|
126
|
-
const [, prop, componentList] = match;
|
|
127
|
-
if (UNIVERSAL_PROPS.has(prop) || NEGATION_PATTERN.test(text)) {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
for (const component of componentList.split(
|
|
131
|
-
/,(?: and| or)?| and | or /,
|
|
132
|
-
)) {
|
|
133
|
-
const name = component.trim();
|
|
134
|
-
if (!name) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const known = documentedProps.get(name);
|
|
138
|
-
if (known && !known.has(prop)) {
|
|
139
|
-
violations.push({
|
|
140
|
-
file: relative(SRC_DIR, file),
|
|
141
|
-
component: name,
|
|
142
|
-
prop,
|
|
143
|
-
excerpt: text.slice(
|
|
144
|
-
Math.max(0, match.index - 40),
|
|
145
|
-
match.index + match[0].length + 20,
|
|
146
|
-
),
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return violations;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
describe('doc prose prop references', () => {
|
|
157
|
-
it('sanity: discovers doc files and documented components', () => {
|
|
158
|
-
expect(docFiles.length).toBeGreaterThan(0);
|
|
159
|
-
expect(documentedProps.size).toBeGreaterThan(0);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('every "X prop on Y" reference names a documented prop of Y', () => {
|
|
163
|
-
const violations = findViolations();
|
|
164
|
-
expect(
|
|
165
|
-
violations,
|
|
166
|
-
violations
|
|
167
|
-
.map(
|
|
168
|
-
v =>
|
|
169
|
-
`${v.file} references prop "${v.prop}" on ${v.component}, ` +
|
|
170
|
-
`which does not document it (…${v.excerpt}…). Either the prop ` +
|
|
171
|
-
`doesn't exist (fix the prose; negate with "no such prop ` +
|
|
172
|
-
`exists" if countering a known hallucination) or it exists in ` +
|
|
173
|
-
`source but is missing from the component's doc props[] (add it).`,
|
|
174
|
-
)
|
|
175
|
-
.join('\n'),
|
|
176
|
-
).toEqual([]);
|
|
177
|
-
});
|
|
178
|
-
});
|
package/src/utils/color.test.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
import {describe, expect, it} from 'vitest';
|
|
4
|
-
import {parseHex, parseRgb, parseColor, formatHex, formatColor} from './color';
|
|
5
|
-
|
|
6
|
-
describe('parseHex', () => {
|
|
7
|
-
it('parses #rrggbb', () => {
|
|
8
|
-
expect(parseHex('#0064E0')).toEqual({r: 0, g: 100, b: 224, a: 1});
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('parses without a leading #', () => {
|
|
12
|
-
expect(parseHex('0064E0')).toEqual({r: 0, g: 100, b: 224, a: 1});
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('expands #rgb shorthand', () => {
|
|
16
|
-
expect(parseHex('#abc')).toEqual({r: 0xaa, g: 0xbb, b: 0xcc, a: 1});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('parses #rrggbbaa alpha', () => {
|
|
20
|
-
expect(parseHex('#00000080')).toEqual({r: 0, g: 0, b: 0, a: 128 / 255});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('expands #rgba shorthand alpha', () => {
|
|
24
|
-
expect(parseHex('#f008')).toEqual({
|
|
25
|
-
r: 0xff,
|
|
26
|
-
g: 0,
|
|
27
|
-
b: 0,
|
|
28
|
-
a: 0x88 / 255,
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('returns null for non-hex input', () => {
|
|
33
|
-
expect(parseHex('rgb(0,0,0)')).toBeNull();
|
|
34
|
-
expect(parseHex('#12')).toBeNull();
|
|
35
|
-
expect(parseHex('#zzzzzz')).toBeNull();
|
|
36
|
-
// @ts-expect-error runtime guard for non-string input
|
|
37
|
-
expect(parseHex(null)).toBeNull();
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('parseRgb', () => {
|
|
42
|
-
it('parses comma-separated rgb()', () => {
|
|
43
|
-
expect(parseRgb('rgb(0, 100, 224)')).toEqual({
|
|
44
|
-
r: 0,
|
|
45
|
-
g: 100,
|
|
46
|
-
b: 224,
|
|
47
|
-
a: 1,
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('parses rgba() with alpha', () => {
|
|
52
|
-
expect(parseRgb('rgba(0, 0, 0, 0.5)')).toEqual({r: 0, g: 0, b: 0, a: 0.5});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('parses space-separated with slash alpha', () => {
|
|
56
|
-
expect(parseRgb('rgb(0 100 224 / 0.25)')).toEqual({
|
|
57
|
-
r: 0,
|
|
58
|
-
g: 100,
|
|
59
|
-
b: 224,
|
|
60
|
-
a: 0.25,
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('parses percentage channels', () => {
|
|
65
|
-
expect(parseRgb('rgb(100%, 0%, 0%)')).toEqual({
|
|
66
|
-
r: 255,
|
|
67
|
-
g: 0,
|
|
68
|
-
b: 0,
|
|
69
|
-
a: 1,
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('clamps alpha and channels into range', () => {
|
|
74
|
-
expect(parseRgb('rgba(300, -20, 10, 2)')).toEqual({
|
|
75
|
-
r: 255,
|
|
76
|
-
g: 0,
|
|
77
|
-
b: 10,
|
|
78
|
-
a: 1,
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('returns null when it cannot parse', () => {
|
|
83
|
-
expect(parseRgb('rgb(0, 0)')).toBeNull();
|
|
84
|
-
expect(parseRgb('#000000')).toBeNull();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('parseColor', () => {
|
|
89
|
-
it('routes hex and rgb strings to the right parser', () => {
|
|
90
|
-
expect(parseColor('#FFFFFF')).toEqual({r: 255, g: 255, b: 255, a: 1});
|
|
91
|
-
expect(parseColor('rgb(1, 2, 3)')).toEqual({r: 1, g: 2, b: 3, a: 1});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('resolves the named colors used in token expressions', () => {
|
|
95
|
-
expect(parseColor('transparent')).toEqual({r: 0, g: 0, b: 0, a: 0});
|
|
96
|
-
expect(parseColor('BLACK')).toEqual({r: 0, g: 0, b: 0, a: 1});
|
|
97
|
-
expect(parseColor('white')).toEqual({r: 255, g: 255, b: 255, a: 1});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('returns null for unsupported colors so callers can preserve them', () => {
|
|
101
|
-
expect(parseColor('var(--color-accent)')).toBeNull();
|
|
102
|
-
expect(parseColor('oklch(0.5 0.1 200)')).toBeNull();
|
|
103
|
-
expect(parseColor('rebeccapurple')).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('formatHex', () => {
|
|
108
|
-
it('formats channels as uppercase #RRGGBB', () => {
|
|
109
|
-
expect(formatHex(0, 100, 224)).toBe('#0064E0');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('rounds and clamps out-of-range channels', () => {
|
|
113
|
-
expect(formatHex(-5, 300, 127.6)).toBe('#00FF80');
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('formatColor', () => {
|
|
118
|
-
it('formats opaque colors as hex', () => {
|
|
119
|
-
expect(formatColor({r: 0, g: 100, b: 224, a: 1})).toBe('#0064E0');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('formats translucent colors as rgba()', () => {
|
|
123
|
-
expect(formatColor({r: 0, g: 100, b: 224, a: 0.2})).toBe(
|
|
124
|
-
'rgba(0, 100, 224, 0.2)',
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('round-trips through parseColor', () => {
|
|
129
|
-
const parsed = parseColor('rgba(10, 20, 30, 0.5)');
|
|
130
|
-
expect(parsed).not.toBeNull();
|
|
131
|
-
expect(formatColor(parsed!)).toBe('rgba(10, 20, 30, 0.5)');
|
|
132
|
-
});
|
|
133
|
-
});
|