@scalewing/react 1.21.0 → 1.23.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/CHANGELOG.md +30 -0
- package/README.md +49 -5
- package/dist/components/ActionMenu.d.ts +13 -1
- package/dist/components/ActionMenu.js +2 -2
- package/dist/components/BarChart.d.ts +28 -0
- package/dist/components/BarChart.js +5 -4
- package/dist/components/CalendarButton.d.ts +10 -0
- package/dist/components/CalendarButton.js +4 -3
- package/dist/components/DenominationGrid.d.ts +2 -2
- package/dist/components/DenominationGrid.js +5 -2
- package/dist/components/Field.d.ts +9 -0
- package/dist/components/Field.js +3 -2
- package/dist/components/SectionNav.d.ts +44 -0
- package/dist/components/SectionNav.js +48 -0
- package/dist/components/Table.d.ts +38 -13
- package/dist/components/Table.js +7 -12
- package/dist/components/Tabs.d.ts +3 -1
- package/dist/components/Tabs.js +8 -2
- package/dist/components/Tooltip.js +19 -3
- package/dist/components/action-menu/ActionMenuList.d.ts +3 -2
- package/dist/components/action-menu/ActionMenuList.js +16 -4
- package/dist/components/date-field/CalendarDialog.d.ts +4 -1
- package/dist/components/date-field/CalendarDialog.js +2 -2
- package/dist/components/date-field/CalendarGrid.d.ts +4 -1
- package/dist/components/date-field/CalendarGrid.js +11 -3
- package/dist/components/denomination-grid/DenominationStrip.d.ts +2 -2
- package/dist/components/denomination-grid/DenominationStrip.js +17 -6
- package/dist/components/denomination-grid/types.d.ts +15 -2
- package/dist/components/table/TableCell.d.ts +42 -0
- package/dist/components/table/TableCell.js +27 -0
- package/dist/components/table/table-layout.d.ts +10 -0
- package/dist/components/table/table-layout.js +14 -0
- package/dist/components/tooltip/tooltip-open.d.ts +8 -0
- package/dist/components/tooltip/tooltip-open.js +60 -0
- package/dist/components/tooltip/use-tooltip-placement.d.ts +11 -0
- package/dist/components/tooltip/use-tooltip-placement.js +46 -0
- package/dist/components/use-anchored-popover.d.ts +2 -0
- package/dist/components/use-anchored-popover.js +2 -1
- package/dist/css/breakpoints.d.ts +24 -3
- package/dist/css/breakpoints.js +27 -1
- package/dist/css/css-action-menu.js +24 -0
- package/dist/css/css-chart.js +45 -1
- package/dist/css/css-chrome.js +13 -0
- package/dist/css/css-components.js +3 -0
- package/dist/css/css-date-field.js +47 -1
- package/dist/css/css-denomination-aligned.d.ts +7 -0
- package/dist/css/css-denomination-aligned.js +68 -0
- package/dist/css/css-denomination-grid.js +7 -1
- package/dist/css/css-field.js +14 -0
- package/dist/css/css-responsive.js +4 -5
- package/dist/css/css-scroll-region.d.ts +8 -0
- package/dist/css/css-scroll-region.js +9 -3
- package/dist/css/css-section-nav.d.ts +4 -0
- package/dist/css/css-section-nav.js +120 -0
- package/dist/css/css-table-layout.d.ts +20 -0
- package/dist/css/css-table-layout.js +59 -0
- package/dist/css/css-table-sort.d.ts +2 -0
- package/dist/css/css-table-sort.js +105 -0
- package/dist/css/css-table.js +8 -0
- package/dist/css/css-tabs.d.ts +6 -1
- package/dist/css/css-tabs.js +9 -1
- package/dist/css/css-tooltip.js +7 -4
- package/dist/css/stylesheet.js +2 -0
- package/dist/date-only.d.ts +12 -0
- package/dist/date-only.js +24 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2 -0
- package/dist/styles.css +383 -6
- package/dist/visibility-classes.d.ts +3 -3
- package/dist/visibility-classes.js +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* While `shown`, puts the tooltip on the top layer as a manual popover and
|
|
4
|
+
* keeps it beside its anchor and inside the viewport: under the anchor (over
|
|
5
|
+
* it when there is no room below), lined up with its start or, where that
|
|
6
|
+
* would leave the screen, its end, a `space-2` inset from every edge. It
|
|
7
|
+
* follows scrolling, resizing, and a change in either element's size. While
|
|
8
|
+
* hidden the tooltip stays in the page (it may name its trigger) and is not
|
|
9
|
+
* a popover. Without the popover API it keeps the same fixed placement.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useTooltipPlacement(shown: boolean, tooltipRef: RefObject<HTMLElement | null>, anchorRef: RefObject<HTMLElement | null>): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useLayoutEffect } from 'react';
|
|
3
|
+
import { placePopover } from '../use-anchored-popover.js';
|
|
4
|
+
/**
|
|
5
|
+
* While `shown`, puts the tooltip on the top layer as a manual popover and
|
|
6
|
+
* keeps it beside its anchor and inside the viewport: under the anchor (over
|
|
7
|
+
* it when there is no room below), lined up with its start or, where that
|
|
8
|
+
* would leave the screen, its end, a `space-2` inset from every edge. It
|
|
9
|
+
* follows scrolling, resizing, and a change in either element's size. While
|
|
10
|
+
* hidden the tooltip stays in the page (it may name its trigger) and is not
|
|
11
|
+
* a popover. Without the popover API it keeps the same fixed placement.
|
|
12
|
+
*/
|
|
13
|
+
export function useTooltipPlacement(shown, tooltipRef, anchorRef) {
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
const node = tooltipRef.current;
|
|
16
|
+
const anchor = anchorRef.current;
|
|
17
|
+
if (!shown || !node || !anchor)
|
|
18
|
+
return;
|
|
19
|
+
const supportsPopover = typeof node.showPopover === 'function';
|
|
20
|
+
if (supportsPopover) {
|
|
21
|
+
node.setAttribute('popover', 'manual');
|
|
22
|
+
if (!node.matches(':popover-open'))
|
|
23
|
+
node.showPopover();
|
|
24
|
+
}
|
|
25
|
+
const place = () => {
|
|
26
|
+
if (node && anchor)
|
|
27
|
+
placePopover(node, anchor, { flipInline: true });
|
|
28
|
+
};
|
|
29
|
+
place();
|
|
30
|
+
const resizes = typeof ResizeObserver === 'function' ? new ResizeObserver(place) : null;
|
|
31
|
+
resizes?.observe(node);
|
|
32
|
+
resizes?.observe(anchor);
|
|
33
|
+
window.addEventListener('resize', place);
|
|
34
|
+
window.addEventListener('scroll', place, true);
|
|
35
|
+
return () => {
|
|
36
|
+
resizes?.disconnect();
|
|
37
|
+
window.removeEventListener('resize', place);
|
|
38
|
+
window.removeEventListener('scroll', place, true);
|
|
39
|
+
if (supportsPopover) {
|
|
40
|
+
if (node.matches(':popover-open'))
|
|
41
|
+
node.hidePopover();
|
|
42
|
+
node.removeAttribute('popover');
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}, [anchorRef, shown, tooltipRef]);
|
|
46
|
+
}
|
|
@@ -6,6 +6,8 @@ export type AnchoredPopoverPlacement = {
|
|
|
6
6
|
/** Line up with the anchor's other edge when the preferred one overflows. */
|
|
7
7
|
flipInline?: boolean;
|
|
8
8
|
};
|
|
9
|
+
/** Places a fixed (or top-layer) `node` beside `anchor`, inside the viewport. */
|
|
10
|
+
export declare function placePopover(node: HTMLElement, anchor: HTMLElement, { align, flipInline }: AnchoredPopoverPlacement): void;
|
|
9
11
|
/**
|
|
10
12
|
* Shows a mounted element on the top layer as a manual popover, keeps it
|
|
11
13
|
* beside `anchorRef` through scroll, window resize, and a change in its own
|
|
@@ -13,7 +13,8 @@ function layoutViewport() {
|
|
|
13
13
|
height: root.clientHeight || window.innerHeight,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
/** Places a fixed (or top-layer) `node` beside `anchor`, inside the viewport. */
|
|
17
|
+
export function placePopover(node, anchor, { align = 'start', flipInline = false }) {
|
|
17
18
|
const position = anchoredPosition(anchor.getBoundingClientRect(), { width: node.offsetWidth, height: node.offsetHeight }, layoutViewport(), {
|
|
18
19
|
gap: spacingScale[1],
|
|
19
20
|
inset: spacingScale[2],
|
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
/** The layout breakpoint: a phone below it, a wider screen from it. */
|
|
1
2
|
export declare const breakpointScale: {
|
|
2
3
|
readonly md: 48;
|
|
3
4
|
};
|
|
4
5
|
export type Breakpoint = keyof typeof breakpointScale;
|
|
5
6
|
export declare const breakpoints: Breakpoint[];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The breakpoints `hideBelow` and `hideFrom` take: `md`, and `lg` (64rem)
|
|
9
|
+
* for content that fits only from a laptop up, such as a row of labelled
|
|
10
|
+
* destinations a tablet shows as glyphs. The layout props (`columnsBelow`,
|
|
11
|
+
* `stickyBelow`, `verticalFrom`, …) keep the one layout breakpoint.
|
|
12
|
+
*/
|
|
13
|
+
export declare const visibilityBreakpointScale: {
|
|
14
|
+
readonly lg: 64;
|
|
15
|
+
readonly md: 48;
|
|
16
|
+
};
|
|
17
|
+
export type VisibilityBreakpoint = keyof typeof visibilityBreakpointScale;
|
|
18
|
+
export declare const visibilityBreakpoints: VisibilityBreakpoint[];
|
|
19
|
+
/** Which side of a breakpoint: `from` it and wider, or `below` it. */
|
|
20
|
+
export type BreakpointDirection = 'from' | 'below';
|
|
21
|
+
export declare const breakpointDirections: readonly BreakpointDirection[];
|
|
22
|
+
export declare function hideClass(direction: BreakpointDirection, breakpoint: VisibilityBreakpoint): string;
|
|
23
|
+
/**
|
|
24
|
+
* The media query `hideBelow` (`'below'`) or `hideFrom` (`'from'`) uses at a
|
|
25
|
+
* breakpoint, such as `not all and (min-width: 64rem)` below `lg`, for an app
|
|
26
|
+
* that must follow the same width in script (a tooltip that names a glyph
|
|
27
|
+
* only while its label is hidden).
|
|
28
|
+
*/
|
|
29
|
+
export declare function breakpointQuery(direction: BreakpointDirection, breakpoint: VisibilityBreakpoint): string;
|
package/dist/css/breakpoints.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
|
+
/** The layout breakpoint: a phone below it, a wider screen from it. */
|
|
1
2
|
export const breakpointScale = {
|
|
2
3
|
md: 48,
|
|
3
4
|
};
|
|
4
5
|
export const breakpoints = Object.keys(breakpointScale);
|
|
6
|
+
/**
|
|
7
|
+
* The breakpoints `hideBelow` and `hideFrom` take: `md`, and `lg` (64rem)
|
|
8
|
+
* for content that fits only from a laptop up, such as a row of labelled
|
|
9
|
+
* destinations a tablet shows as glyphs. The layout props (`columnsBelow`,
|
|
10
|
+
* `stickyBelow`, `verticalFrom`, …) keep the one layout breakpoint.
|
|
11
|
+
*/
|
|
12
|
+
export const visibilityBreakpointScale = {
|
|
13
|
+
...breakpointScale,
|
|
14
|
+
lg: 64,
|
|
15
|
+
};
|
|
16
|
+
export const visibilityBreakpoints = Object.keys(visibilityBreakpointScale);
|
|
17
|
+
export const breakpointDirections = [
|
|
18
|
+
'from',
|
|
19
|
+
'below',
|
|
20
|
+
];
|
|
5
21
|
export function hideClass(direction, breakpoint) {
|
|
6
22
|
return `sw-hide-${direction}-${breakpoint}`;
|
|
7
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The media query `hideBelow` (`'below'`) or `hideFrom` (`'from'`) uses at a
|
|
26
|
+
* breakpoint, such as `not all and (min-width: 64rem)` below `lg`, for an app
|
|
27
|
+
* that must follow the same width in script (a tooltip that names a glyph
|
|
28
|
+
* only while its label is hidden).
|
|
29
|
+
*/
|
|
8
30
|
export function breakpointQuery(direction, breakpoint) {
|
|
9
|
-
|
|
31
|
+
if (!breakpointDirections.includes(direction))
|
|
32
|
+
throw new RangeError(`direction must be one of ${breakpointDirections.join(', ')}`);
|
|
33
|
+
if (!visibilityBreakpoints.includes(breakpoint))
|
|
34
|
+
throw new RangeError(`breakpoint must be one of ${visibilityBreakpoints.join(', ')}`);
|
|
35
|
+
const minWidth = `(min-width: ${visibilityBreakpointScale[breakpoint]}rem)`;
|
|
10
36
|
return direction === 'from' ? minWidth : `not all and ${minWidth}`;
|
|
11
37
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { zIndex } from './stacking.js';
|
|
3
3
|
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
4
|
+
const caption = typographyVariants.caption;
|
|
4
5
|
const label = typographyVariants.label;
|
|
5
6
|
export function cssActionMenuClasses() {
|
|
6
7
|
return `.sw-action-menu {
|
|
@@ -56,6 +57,27 @@ export function cssActionMenuClasses() {
|
|
|
56
57
|
${zIndex('popup')}
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/* Each child of the header is a line of its own: a name, then a role. */
|
|
61
|
+
.sw-action-menu-header {
|
|
62
|
+
border-bottom: 1px solid var(--sw-color-border);
|
|
63
|
+
color: var(--sw-color-muted);
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: var(--sw-space-1);
|
|
67
|
+
font-family: var(--sw-font-sans);
|
|
68
|
+
font-size: ${caption.fontSize}px;
|
|
69
|
+
letter-spacing: ${caption.letterSpacing}px;
|
|
70
|
+
line-height: ${caption.lineHeight}px;
|
|
71
|
+
margin-bottom: var(--sw-space-1);
|
|
72
|
+
/* The commands' inline padding, so the header's words line up with theirs. */
|
|
73
|
+
padding: var(--sw-space-1) var(--sw-control-xs-padding-inline) var(--sw-space-2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* With a header, the commands are the menu inside the popover. */
|
|
77
|
+
.sw-action-menu-items {
|
|
78
|
+
display: block;
|
|
79
|
+
}
|
|
80
|
+
|
|
59
81
|
.sw-action-menu-item {
|
|
60
82
|
align-items: center;
|
|
61
83
|
appearance: none;
|
|
@@ -109,6 +131,8 @@ export function actionMenuClassCatalog() {
|
|
|
109
131
|
'sw-action-menu',
|
|
110
132
|
'sw-action-menu-trigger',
|
|
111
133
|
'sw-action-menu-list',
|
|
134
|
+
'sw-action-menu-header',
|
|
135
|
+
'sw-action-menu-items',
|
|
112
136
|
'sw-action-menu-item',
|
|
113
137
|
'sw-action-menu-item-danger',
|
|
114
138
|
];
|
package/dist/css/css-chart.js
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
const caption = typographyVariants.caption;
|
|
3
3
|
const data = typographyVariants.data;
|
|
4
|
+
/*
|
|
5
|
+
* A diverging chart puts zero in the middle of each track, marked by a
|
|
6
|
+
* hairline: a positive bar starts there and grows toward the inline end, a
|
|
7
|
+
* negative one ends there and grows toward the start, each at most half the
|
|
8
|
+
* track. The fill keeps its share of the scale (`--sw-bar-fill`), so a
|
|
9
|
+
* bar is half as long as in the magnitude layout, on the same scale. The
|
|
10
|
+
* end at zero is square, so the bars meet the hairline.
|
|
11
|
+
*/
|
|
12
|
+
function divergingRules() {
|
|
13
|
+
return `.sw-bar-chart-diverging .sw-bar-chart-track {
|
|
14
|
+
position: relative;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.sw-bar-chart-diverging .sw-bar-chart-track::before {
|
|
18
|
+
background: var(--sw-color-border);
|
|
19
|
+
content: '';
|
|
20
|
+
inset-block: 0;
|
|
21
|
+
inset-inline-start: 50%;
|
|
22
|
+
position: absolute;
|
|
23
|
+
width: 1px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sw-bar-chart-diverging .sw-bar-chart-fill {
|
|
27
|
+
border-end-start-radius: 0;
|
|
28
|
+
border-start-start-radius: 0;
|
|
29
|
+
margin-inline-start: 50%;
|
|
30
|
+
width: calc(var(--sw-bar-fill, 0) * 50%);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sw-bar-chart-diverging .sw-bar-chart-fill-negative {
|
|
34
|
+
border-end-end-radius: 0;
|
|
35
|
+
border-end-start-radius: inherit;
|
|
36
|
+
border-start-end-radius: 0;
|
|
37
|
+
border-start-start-radius: inherit;
|
|
38
|
+
margin-inline-start: calc(50% - var(--sw-bar-fill, 0) * 50%);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (forced-colors: active) {
|
|
42
|
+
.sw-bar-chart-diverging .sw-bar-chart-track::before { background: CanvasText; }
|
|
43
|
+
}`;
|
|
44
|
+
}
|
|
4
45
|
export function cssChartClasses() {
|
|
5
46
|
return `.sw-bar-chart {
|
|
6
47
|
display: grid;
|
|
@@ -80,7 +121,9 @@ export function cssChartClasses() {
|
|
|
80
121
|
.sw-bar-chart-axis-track {
|
|
81
122
|
display: flex;
|
|
82
123
|
justify-content: space-between;
|
|
83
|
-
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
${divergingRules()}`;
|
|
84
127
|
}
|
|
85
128
|
export function chartClassCatalog() {
|
|
86
129
|
return [
|
|
@@ -94,5 +137,6 @@ export function chartClassCatalog() {
|
|
|
94
137
|
'sw-bar-chart-fill-negative',
|
|
95
138
|
'sw-bar-chart-value',
|
|
96
139
|
'sw-bar-chart-axis-track',
|
|
140
|
+
'sw-bar-chart-diverging',
|
|
97
141
|
];
|
|
98
142
|
}
|
package/dist/css/css-chrome.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { zIndex } from './stacking.js';
|
|
3
|
+
import { coarsePointerQuery, touchTarget } from './touch-target.js';
|
|
3
4
|
const caption = typographyVariants.caption;
|
|
4
5
|
const label = typographyVariants.label;
|
|
5
6
|
export function cssChromeClasses() {
|
|
@@ -36,6 +37,18 @@ export function cssChromeClasses() {
|
|
|
36
37
|
color: var(--sw-color-text);
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/*
|
|
41
|
+
* A button in a navigation is a destination: on a coarse pointer it grows to
|
|
42
|
+
* the 44 px touch target both ways, whatever its size, as a section link
|
|
43
|
+
* and a calendar button do. A label beside its glyph keeps its line.
|
|
44
|
+
*/
|
|
45
|
+
@media ${coarsePointerQuery} {
|
|
46
|
+
.sw-nav .sw-button {
|
|
47
|
+
min-height: ${touchTarget};
|
|
48
|
+
min-width: ${touchTarget};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
.sw-field-xs :is(select, input, textarea) {
|
|
40
53
|
font-size: ${caption.fontSize}px;
|
|
41
54
|
letter-spacing: ${caption.letterSpacing}px;
|
|
@@ -7,6 +7,7 @@ import { cssChartClasses } from './css-chart.js';
|
|
|
7
7
|
import { cssCheckboxClasses } from './css-checkbox.js';
|
|
8
8
|
import { cssRadioGroupClasses } from './css-radio-group.js';
|
|
9
9
|
import { cssChromeClasses } from './css-chrome.js';
|
|
10
|
+
import { cssSectionNavClasses } from './css-section-nav.js';
|
|
10
11
|
import { cssDataClasses } from './css-data.js';
|
|
11
12
|
import { cssSegmentedClasses } from './css-segmented.js';
|
|
12
13
|
import { cssTableClasses } from './css-table.js';
|
|
@@ -123,6 +124,8 @@ ${cssChartClasses()}
|
|
|
123
124
|
|
|
124
125
|
${cssChromeClasses()}
|
|
125
126
|
|
|
127
|
+
${cssSectionNavClasses()}
|
|
128
|
+
|
|
126
129
|
${cssDialogClasses()}
|
|
127
130
|
|
|
128
131
|
${cssAccordionClasses()}
|
|
@@ -229,16 +229,51 @@ function calendarRules() {
|
|
|
229
229
|
background: color-mix(in srgb, var(--sw-color-muted) 16%, transparent);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
/*
|
|
233
|
+
* A range's days are one band in each week row: square-cornered, the accent
|
|
234
|
+
* at 16 % over the calendar's glass, rounded at the range's start and end.
|
|
235
|
+
* accentSubtle is mixed against the surface, not the glass, and fades to
|
|
236
|
+
* nothing where the accent is near AA, so the band would vanish in most
|
|
237
|
+
* dark palettes. Every number on the band is in the text colour, which
|
|
238
|
+
* keeps 4.5:1 on it in every palette (muted and accent do not); a
|
|
239
|
+
* neighbouring month's day and today are marked by the band and today's
|
|
240
|
+
* accent ring (3:1 or more on the band). The selected day sits on top.
|
|
241
|
+
*/
|
|
242
|
+
.sw-date-field-day-in-range {
|
|
243
|
+
background: color-mix(in srgb, var(--sw-color-accent) 16%, transparent);
|
|
244
|
+
border-radius: 0;
|
|
245
|
+
color: var(--sw-color-text);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.sw-date-field-day-range-start {
|
|
249
|
+
border-end-start-radius: var(--sw-radius-pill);
|
|
250
|
+
border-start-start-radius: var(--sw-radius-pill);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.sw-date-field-day-range-end {
|
|
254
|
+
border-end-end-radius: var(--sw-radius-pill);
|
|
255
|
+
border-start-end-radius: var(--sw-radius-pill);
|
|
256
|
+
}
|
|
257
|
+
|
|
232
258
|
.sw-date-field-day[aria-current='date'] {
|
|
233
259
|
border-color: var(--sw-color-accent);
|
|
234
260
|
color: var(--sw-color-accent);
|
|
235
261
|
font-weight: ${label.fontWeight};
|
|
236
262
|
}
|
|
237
263
|
|
|
264
|
+
.sw-date-field-day-in-range[aria-current='date'] {
|
|
265
|
+
color: var(--sw-color-text);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.sw-date-field-day-in-range:hover:not([aria-disabled='true']) {
|
|
269
|
+
background: color-mix(in srgb, var(--sw-color-accent) 24%, transparent);
|
|
270
|
+
}
|
|
271
|
+
|
|
238
272
|
.sw-date-field-day[aria-selected='true'],
|
|
239
273
|
.sw-date-field-day[aria-selected='true']:hover {
|
|
240
274
|
background: var(--sw-color-accent);
|
|
241
275
|
border-color: var(--sw-color-accent);
|
|
276
|
+
border-radius: var(--sw-radius-pill);
|
|
242
277
|
color: var(--sw-color-onAccent);
|
|
243
278
|
font-weight: ${label.fontWeight};
|
|
244
279
|
}
|
|
@@ -279,7 +314,15 @@ function adaptiveRules() {
|
|
|
279
314
|
}
|
|
280
315
|
.sw-date-field-calendar { border-color: CanvasText; }
|
|
281
316
|
.sw-date-field-day[aria-current='date'] { border-color: CanvasText; }
|
|
282
|
-
.sw-date-field-day
|
|
317
|
+
.sw-date-field-day-in-range,
|
|
318
|
+
.sw-date-field-day-in-range:hover:not([aria-disabled='true']),
|
|
319
|
+
.sw-date-field-day-in-range[aria-current='date'] {
|
|
320
|
+
background: Mark;
|
|
321
|
+
color: MarkText;
|
|
322
|
+
forced-color-adjust: none;
|
|
323
|
+
}
|
|
324
|
+
.sw-date-field-day[aria-selected='true'],
|
|
325
|
+
.sw-date-field-day[aria-selected='true']:hover {
|
|
283
326
|
background: Highlight;
|
|
284
327
|
color: HighlightText;
|
|
285
328
|
forced-color-adjust: none;
|
|
@@ -311,6 +354,9 @@ export function dateFieldClassCatalog() {
|
|
|
311
354
|
'sw-date-field-grid',
|
|
312
355
|
'sw-date-field-day',
|
|
313
356
|
'sw-date-field-day-outside',
|
|
357
|
+
'sw-date-field-day-in-range',
|
|
358
|
+
'sw-date-field-day-range-start',
|
|
359
|
+
'sw-date-field-day-range-end',
|
|
314
360
|
'sw-date-field-footer',
|
|
315
361
|
];
|
|
316
362
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type TableColumnSize } from './css-table-layout.js';
|
|
2
|
+
/** The row-label widths a strip lines up at: the Table column sizes. */
|
|
3
|
+
export type DenominationLabelWidth = TableColumnSize;
|
|
4
|
+
export declare const denominationLabelWidths: DenominationLabelWidth[];
|
|
5
|
+
export declare function denominationLabelWidthClass(width: DenominationLabelWidth): string;
|
|
6
|
+
export declare function cssDenominationAlignedClasses(): string;
|
|
7
|
+
export declare function denominationAlignedClassCatalog(): string[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { breakpointQuery } from './breakpoints.js';
|
|
2
|
+
import { tableColumnSizes } from './css-table-layout.js';
|
|
3
|
+
export const denominationLabelWidths = Object.keys(tableColumnSizes);
|
|
4
|
+
export function denominationLabelWidthClass(width) {
|
|
5
|
+
return `sw-denomination-label-${width}`;
|
|
6
|
+
}
|
|
7
|
+
/*
|
|
8
|
+
* A strip with a label width lines its columns up with every other strip
|
|
9
|
+
* of the same columns in a container of the same width, such as the cards
|
|
10
|
+
* of a feed: the label column is that width, and the count columns (and the
|
|
11
|
+
* total column from md up, where it is drawn) share the rest of the scroll
|
|
12
|
+
* region's width equally, measured from the region itself (a container, so
|
|
13
|
+
* 100cqi is its width) rather than from the cells' content. Every column is
|
|
14
|
+
* sized, so the browser has no spare width to share by content. A count
|
|
15
|
+
* wider than its share widens its column, and the strip then scrolls as a
|
|
16
|
+
* plain one does. The component sets the column count and whether there is
|
|
17
|
+
* a total column (`--sw-denomination-columns`, `--sw-denomination-total`);
|
|
18
|
+
* below md the total column collapses, so it takes no share.
|
|
19
|
+
*/
|
|
20
|
+
export function cssDenominationAlignedClasses() {
|
|
21
|
+
const widths = Object.entries(tableColumnSizes)
|
|
22
|
+
.map(([size, rem]) => `.${denominationLabelWidthClass(size)} { --sw-denomination-label-width: ${rem}rem; }`)
|
|
23
|
+
.join('\n');
|
|
24
|
+
const strip = '.sw-denomination-strip-aligned';
|
|
25
|
+
return `.sw-denomination-scroll-aligned {
|
|
26
|
+
container-type: inline-size;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
${widths}
|
|
30
|
+
|
|
31
|
+
${strip} {
|
|
32
|
+
--sw-denomination-total-shown: 1;
|
|
33
|
+
--sw-denomination-share: calc(
|
|
34
|
+
(100cqi - var(--sw-denomination-label-width)) /
|
|
35
|
+
(var(--sw-denomination-columns) + var(--sw-denomination-total) * var(--sw-denomination-total-shown))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
${strip} th,
|
|
40
|
+
${strip} td {
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
${strip} .sw-denomination-label,
|
|
45
|
+
${strip} thead .sw-denomination-corner:first-child {
|
|
46
|
+
width: var(--sw-denomination-label-width);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
${strip} .sw-denomination-cell,
|
|
50
|
+
${strip} .sw-denomination-head:not(.sw-denomination-total-head),
|
|
51
|
+
${strip} .sw-denomination-total,
|
|
52
|
+
${strip} .sw-denomination-total-head {
|
|
53
|
+
width: var(--sw-denomination-share);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@media ${breakpointQuery('below', 'md')} {
|
|
57
|
+
${strip} { --sw-denomination-total-shown: 0; }
|
|
58
|
+
${strip} .sw-denomination-total,
|
|
59
|
+
${strip} .sw-denomination-total-head { width: 0; }
|
|
60
|
+
}`;
|
|
61
|
+
}
|
|
62
|
+
export function denominationAlignedClassCatalog() {
|
|
63
|
+
return [
|
|
64
|
+
'sw-denomination-scroll-aligned',
|
|
65
|
+
'sw-denomination-strip-aligned',
|
|
66
|
+
...denominationLabelWidths.map(denominationLabelWidthClass),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typographyVariants } from '@scalewing/tokens';
|
|
2
2
|
import { breakpointQuery } from './breakpoints.js';
|
|
3
3
|
import { badgeTones } from './css-data.js';
|
|
4
|
+
import { cssDenominationAlignedClasses, denominationAlignedClassCatalog, } from './css-denomination-aligned.js';
|
|
4
5
|
import { cssDenominationCellToneClasses, denominationCellToneClassCatalog, } from './css-denomination-cell-tones.js';
|
|
5
6
|
import { pinnedStartShadeRules, scrollRegionRules, } from './css-scroll-region.js';
|
|
6
7
|
import { visuallyHiddenDeclarations } from './css-utilities.js';
|
|
@@ -235,7 +236,8 @@ ${pinnedStartShadeRules('.sw-denomination-scroll', [
|
|
|
235
236
|
@media ${breakpointQuery('below', 'md')} {
|
|
236
237
|
.sw-denomination-strip .sw-denomination-total,
|
|
237
238
|
.sw-denomination-strip .sw-denomination-total-head { padding: 0; position: relative; }
|
|
238
|
-
.sw-denomination-total-value
|
|
239
|
+
.sw-denomination-total-value,
|
|
240
|
+
.sw-denomination-total-label { ${visuallyHiddenDeclarations} }
|
|
239
241
|
.sw-denomination-total-inline { display: block; }
|
|
240
242
|
.sw-denomination-label-body { flex-direction: column; align-items: flex-start; gap: var(--sw-space-1); }
|
|
241
243
|
.sw-denomination-strip .sw-denomination-label-line { gap: var(--sw-space-1); }
|
|
@@ -247,6 +249,8 @@ ${pinnedStartShadeRules('.sw-denomination-scroll', [
|
|
|
247
249
|
|
|
248
250
|
${cssDenominationCellToneClasses()}
|
|
249
251
|
|
|
252
|
+
${cssDenominationAlignedClasses()}
|
|
253
|
+
|
|
250
254
|
@media (forced-colors: active) {
|
|
251
255
|
.sw-denomination-label { border-inline-start-color: CanvasText; color: CanvasText; }
|
|
252
256
|
.sw-denomination-strip .sw-denomination-label::before { background: CanvasText; forced-color-adjust: none; }
|
|
@@ -279,11 +283,13 @@ export function denominationGridClassCatalog() {
|
|
|
279
283
|
'sw-denomination-total',
|
|
280
284
|
'sw-denomination-total-inline',
|
|
281
285
|
'sw-denomination-total-head',
|
|
286
|
+
'sw-denomination-total-label',
|
|
282
287
|
'sw-denomination-total-value',
|
|
283
288
|
'sw-denomination-tiles',
|
|
284
289
|
'sw-denomination-tile-list',
|
|
285
290
|
'sw-denomination-tile',
|
|
286
291
|
'sw-denomination-subtotal',
|
|
287
292
|
...denominationCellToneClassCatalog(),
|
|
293
|
+
...denominationAlignedClassCatalog(),
|
|
288
294
|
];
|
|
289
295
|
}
|
package/dist/css/css-field.js
CHANGED
|
@@ -80,11 +80,24 @@ export function cssFieldClasses() {
|
|
|
80
80
|
border-color: var(--sw-color-danger);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/*
|
|
84
|
+
* A changed value: the accent border, a hairline thicker through an inset
|
|
85
|
+
* shadow so nothing moves, on the control or on an adorned frame (whose
|
|
86
|
+
* input draws no border of its own).
|
|
87
|
+
*/
|
|
88
|
+
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
89
|
+
.sw-field-changed .sw-field-adorned {
|
|
90
|
+
border-color: var(--sw-color-accent);
|
|
91
|
+
box-shadow: inset 0 0 0 1px var(--sw-color-accent);
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
${adornmentRules()}
|
|
84
95
|
|
|
85
96
|
@media (forced-colors: active) {
|
|
86
97
|
[data-theme] .sw-field-invalid :is(input, select, textarea),
|
|
87
98
|
.sw-field-invalid .sw-field-adorned { border-color: Mark; }
|
|
99
|
+
[data-theme] .sw-field-changed > :is(input, select, textarea),
|
|
100
|
+
.sw-field-changed .sw-field-adorned { border-color: Highlight; box-shadow: inset 0 0 0 1px Highlight; }
|
|
88
101
|
.sw-field-prefix, .sw-field-suffix { color: CanvasText; }
|
|
89
102
|
.sw-field-adorned:has(> input:disabled) {
|
|
90
103
|
${disabledControlForcedColors}
|
|
@@ -99,6 +112,7 @@ export function fieldClassCatalog() {
|
|
|
99
112
|
'sw-field-description',
|
|
100
113
|
'sw-field-error',
|
|
101
114
|
'sw-field-invalid',
|
|
115
|
+
'sw-field-changed',
|
|
102
116
|
'sw-field-adorned',
|
|
103
117
|
'sw-field-prefix',
|
|
104
118
|
'sw-field-suffix',
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const directions = ['from', 'below'];
|
|
1
|
+
import { breakpointDirections, breakpointQuery, hideClass, visibilityBreakpoints, } from './breakpoints.js';
|
|
3
2
|
export function cssResponsiveClasses() {
|
|
4
|
-
return
|
|
5
|
-
.flatMap((breakpoint) =>
|
|
3
|
+
return visibilityBreakpoints
|
|
4
|
+
.flatMap((breakpoint) => breakpointDirections.map((direction) => `@media ${breakpointQuery(direction, breakpoint)} {
|
|
6
5
|
.${hideClass(direction, breakpoint)} { display: none; }
|
|
7
6
|
}`))
|
|
8
7
|
.join('\n\n');
|
|
9
8
|
}
|
|
10
9
|
export function responsiveClassCatalog() {
|
|
11
|
-
return
|
|
10
|
+
return visibilityBreakpoints.flatMap((breakpoint) => breakpointDirections.map((direction) => hideClass(direction, breakpoint)));
|
|
12
11
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shade each inline edge of `selector` draws while `useScrollOverflow`
|
|
3
|
+
* marks it (`sw-scroll-more-start`, `sw-scroll-more-end`). The start edge is
|
|
4
|
+
* the left in a left-to-right box and the right in RTL. A box that scrolls
|
|
5
|
+
* itself (a `Tabs` strip) takes these alone; a scroll region takes them
|
|
6
|
+
* through `scrollRegionRules`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function scrollShadeRules(selector: string): string;
|
|
1
9
|
/**
|
|
2
10
|
* A scroll region is a full-width box that scrolls its own content when the
|
|
3
11
|
* content is wider (or taller) than the box, so a wide table never widens the
|
|
@@ -12,8 +12,14 @@ function edgeShade(side) {
|
|
|
12
12
|
const offset = side === 'left' ? 'var(--sw-space-6)' : 'calc(-1 * var(--sw-space-6))';
|
|
13
13
|
return `inset ${offset} 0 var(--sw-space-5) calc(-1 * var(--sw-space-5)) ${shadeColor}`;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The shade each inline edge of `selector` draws while `useScrollOverflow`
|
|
17
|
+
* marks it (`sw-scroll-more-start`, `sw-scroll-more-end`). The start edge is
|
|
18
|
+
* the left in a left-to-right box and the right in RTL. A box that scrolls
|
|
19
|
+
* itself (a `Tabs` strip) takes these alone; a scroll region takes them
|
|
20
|
+
* through `scrollRegionRules`.
|
|
21
|
+
*/
|
|
22
|
+
export function scrollShadeRules(selector) {
|
|
17
23
|
const more = (edge) => `${selector}.sw-scroll-more-${edge}`;
|
|
18
24
|
const both = `${more('start')}.sw-scroll-more-end`;
|
|
19
25
|
return [
|
|
@@ -47,7 +53,7 @@ ${selector}:focus-visible {
|
|
|
47
53
|
outline-offset: calc(-1 * var(--sw-focus-ring-width));
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
${
|
|
56
|
+
${scrollShadeRules(selector)}`;
|
|
51
57
|
}
|
|
52
58
|
export function scrollRegionClassCatalog() {
|
|
53
59
|
return ['sw-scroll-more-start', 'sw-scroll-more-end'];
|