@scalewing/react 1.39.0 → 1.40.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 +9 -0
- package/README.md +102 -0
- package/dist/calendar-month.d.ts +6 -0
- package/dist/calendar-month.js +15 -0
- package/dist/components/DateRangeButton.d.ts +65 -0
- package/dist/components/DateRangeButton.js +54 -0
- package/dist/components/ListGroup.d.ts +33 -0
- package/dist/components/ListGroup.js +19 -0
- package/dist/components/ListRow.d.ts +51 -0
- package/dist/components/ListRow.js +59 -0
- package/dist/components/NavCount.d.ts +21 -0
- package/dist/components/NavCount.js +23 -0
- package/dist/components/SectionNav.js +1 -8
- package/dist/components/StatTile.d.ts +11 -4
- package/dist/components/StatTile.js +11 -4
- package/dist/components/{date-field → calendar}/CalendarGrid.d.ts +17 -2
- package/dist/components/{date-field → calendar}/CalendarGrid.js +11 -3
- package/dist/components/calendar/CalendarPopover.d.ts +25 -0
- package/dist/components/calendar/CalendarPopover.js +22 -0
- package/dist/components/calendar/MonthStep.d.ts +16 -0
- package/dist/components/calendar/MonthStep.js +17 -0
- package/dist/components/calendar/use-grid-focus.d.ts +18 -0
- package/dist/components/calendar/use-grid-focus.js +35 -0
- package/dist/components/calendar/wrap-tab.d.ts +7 -0
- package/dist/components/calendar/wrap-tab.js +29 -0
- package/dist/components/date-field/CalendarDialog.js +9 -67
- package/dist/components/date-field/CalendarHeader.js +2 -11
- package/dist/components/date-range/DateRangePopover.d.ts +7 -0
- package/dist/components/date-range/DateRangePopover.js +17 -0
- package/dist/components/date-range/DateRangeSheet.d.ts +7 -0
- package/dist/components/date-range/DateRangeSheet.js +15 -0
- package/dist/components/date-range/RangeMonths.d.ts +28 -0
- package/dist/components/date-range/RangeMonths.js +50 -0
- package/dist/components/date-range/RangePresets.d.ts +16 -0
- package/dist/components/date-range/RangePresets.js +10 -0
- package/dist/components/date-range/RangeSummary.d.ts +15 -0
- package/dist/components/date-range/RangeSummary.js +11 -0
- package/dist/components/date-range/range-panel-props.d.ts +31 -0
- package/dist/components/date-range/range-panel-props.js +1 -0
- package/dist/components/date-range/use-range-popup.d.ts +21 -0
- package/dist/components/date-range/use-range-popup.js +53 -0
- package/dist/components/link-press.d.ts +7 -0
- package/dist/components/link-press.js +13 -0
- package/dist/components/list/ListRowAccessoryMark.d.ts +5 -0
- package/dist/components/list/ListRowAccessoryMark.js +7 -0
- package/dist/components/list/ListRowContent.d.ts +20 -0
- package/dist/components/list/ListRowContent.js +10 -0
- package/dist/components/list/list-row-container.d.ts +5 -0
- package/dist/components/list/list-row-container.js +6 -0
- package/dist/components/use-media-query.d.ts +5 -0
- package/dist/components/use-media-query.js +18 -0
- package/dist/css/chevron.d.ts +6 -0
- package/dist/css/chevron.js +14 -0
- package/dist/css/css-button-group-attached.js +8 -4
- package/dist/css/css-button-group-track.js +1 -1
- package/dist/css/css-checkbox.js +2 -2
- package/dist/css/css-components.js +9 -0
- package/dist/css/css-count-pill.d.ts +2 -0
- package/dist/css/css-count-pill.js +54 -0
- package/dist/css/css-date-field.js +3 -2
- package/dist/css/css-date-range-presets.d.ts +2 -0
- package/dist/css/css-date-range-presets.js +109 -0
- package/dist/css/css-date-range.d.ts +2 -0
- package/dist/css/css-date-range.js +197 -0
- package/dist/css/css-list-row.d.ts +2 -0
- package/dist/css/css-list-row.js +193 -0
- package/dist/css/css-list.d.ts +3 -0
- package/dist/css/css-list.js +85 -0
- package/dist/css/css-scroll-region.d.ts +18 -0
- package/dist/css/css-scroll-region.js +41 -0
- package/dist/css/css-section-nav-filled.js +2 -25
- package/dist/css/css-stat-tile.d.ts +7 -0
- package/dist/css/css-stat-tile.js +16 -0
- package/dist/css/css-steps.js +2 -2
- package/dist/css/css-table-bounded-edges.js +4 -27
- package/dist/css/range-band.d.ts +2 -0
- package/dist/css/range-band.js +7 -0
- package/dist/css/stacking.js +2 -2
- package/dist/css/stylesheet.js +6 -0
- package/dist/date-range-draft.d.ts +26 -0
- package/dist/date-range-draft.js +36 -0
- package/dist/date-range-input.d.ts +26 -0
- package/dist/date-range-input.js +58 -0
- package/dist/date-range-labels.d.ts +22 -0
- package/dist/date-range-labels.js +26 -0
- package/dist/date-range-view.d.ts +12 -0
- package/dist/date-range-view.js +29 -0
- package/dist/date-range-words.d.ts +11 -0
- package/dist/date-range-words.js +40 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/list-options.d.ts +70 -0
- package/dist/list-options.js +77 -0
- package/dist/styles.css +507 -26
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { blockEdgeShadeRules, blockShade } from './css-scroll-region.js';
|
|
2
2
|
const region = '.sw-table-bounded';
|
|
3
3
|
/*
|
|
4
4
|
* While rows are scrolled out past the top or bottom of a bounded region
|
|
@@ -17,7 +17,6 @@ const region = '.sw-table-bounded';
|
|
|
17
17
|
* is. Forced colours draw no shade; the system scrollbar is the cue.
|
|
18
18
|
*/
|
|
19
19
|
function shadeRules() {
|
|
20
|
-
const shade = (direction) => `linear-gradient(to ${direction}, ${scrollShadeColor}, transparent)`;
|
|
21
20
|
const headCells = `${region}.sw-scroll-more-top > .sw-table-sticky > thead > tr:last-child > :is(th, td)::before`;
|
|
22
21
|
const footCells = `${region}.sw-scroll-more-bottom > .sw-table > .sw-table-footer-sticky > tr:first-child > :is(th, td)::before`;
|
|
23
22
|
const noHead = ':not(:has(> .sw-table > thead))';
|
|
@@ -26,7 +25,7 @@ function shadeRules() {
|
|
|
26
25
|
const ownBottom = `${region}.sw-scroll-more-bottom${noPinnedFoot}`;
|
|
27
26
|
const ownBoth = `${region}.sw-scroll-more-top.sw-scroll-more-bottom${noHead}${noPinnedFoot}`;
|
|
28
27
|
return `${headCells} {
|
|
29
|
-
background: ${
|
|
28
|
+
background: ${blockShade('bottom')};
|
|
30
29
|
border-bottom: 0;
|
|
31
30
|
border-top: 1px solid var(--sw-color-border);
|
|
32
31
|
box-sizing: border-box;
|
|
@@ -36,7 +35,7 @@ function shadeRules() {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
${footCells} {
|
|
39
|
-
background: ${
|
|
38
|
+
background: ${blockShade('top')};
|
|
40
39
|
border-bottom: 2px solid var(--sw-color-border);
|
|
41
40
|
border-top: 0;
|
|
42
41
|
box-sizing: border-box;
|
|
@@ -45,33 +44,11 @@ ${footCells} {
|
|
|
45
44
|
inset-block-start: auto;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
${ownTop}
|
|
49
|
-
${ownBottom} {
|
|
50
|
-
background-repeat: no-repeat;
|
|
51
|
-
background-size: 100% var(--sw-space-5);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
${ownTop} {
|
|
55
|
-
background-image: ${shade('bottom')};
|
|
56
|
-
background-position: top;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
${ownBottom} {
|
|
60
|
-
background-image: ${shade('top')};
|
|
61
|
-
background-position: bottom;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
${ownBoth} {
|
|
65
|
-
background-image: ${shade('bottom')}, ${shade('top')};
|
|
66
|
-
background-position: top, bottom;
|
|
67
|
-
}
|
|
47
|
+
${blockEdgeShadeRules({ top: ownTop, bottom: ownBottom, both: ownBoth })}
|
|
68
48
|
|
|
69
49
|
@media (forced-colors: active) {
|
|
70
50
|
${headCells},
|
|
71
51
|
${footCells} { background: none; border-color: CanvasText; }
|
|
72
|
-
${ownTop},
|
|
73
|
-
${ownBottom},
|
|
74
|
-
${ownBoth} { background-image: none; }
|
|
75
52
|
}`;
|
|
76
53
|
}
|
|
77
54
|
/*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A range's band in a calendar: the accent at 16 % over the calendar's
|
|
3
|
+
* glass, 24 % under the pointer. CalendarButton's tint and DateRangeButton's
|
|
4
|
+
* days between a range's ends share it, so the two read as one band.
|
|
5
|
+
*/
|
|
6
|
+
export const rangeBand = 'color-mix(in srgb, var(--sw-color-accent) 16%, transparent)';
|
|
7
|
+
export const rangeBandHover = 'color-mix(in srgb, var(--sw-color-accent) 24%, transparent)';
|
package/dist/css/stacking.js
CHANGED
|
@@ -58,8 +58,8 @@ const openPopups = [
|
|
|
58
58
|
// popover API exists.
|
|
59
59
|
'.sw-combobox-popup',
|
|
60
60
|
'.sw-tooltip:not([hidden])',
|
|
61
|
-
// DateField and
|
|
62
|
-
// top layer where the popover API exists.
|
|
61
|
+
// DateField, CalendarButton and DateRangeButton share it. Rendered only
|
|
62
|
+
// while open; on the top layer where the popover API exists.
|
|
63
63
|
'.sw-date-field-calendar',
|
|
64
64
|
].join(', ');
|
|
65
65
|
const popupHosts = ['.sw-card-glass', '.sw-accordion'];
|
package/dist/css/stylesheet.js
CHANGED
|
@@ -6,6 +6,9 @@ import { accordionClassCatalog } from './css-accordion.js';
|
|
|
6
6
|
import { switchClassCatalog } from './css-switch.js';
|
|
7
7
|
import { dateFieldClassCatalog } from './css-date-field.js';
|
|
8
8
|
import { calendarButtonClassCatalog } from './css-calendar-button.js';
|
|
9
|
+
import { dateRangeClassCatalog } from './css-date-range.js';
|
|
10
|
+
import { listClassCatalog } from './css-list.js';
|
|
11
|
+
import { countPillClassCatalog } from './css-count-pill.js';
|
|
9
12
|
import { checkboxClassCatalog } from './css-checkbox.js';
|
|
10
13
|
import { radioGroupClassCatalog } from './css-radio-group.js';
|
|
11
14
|
import { spinnerClassCatalog } from './css-spinner.js';
|
|
@@ -95,6 +98,9 @@ export function utilityClassCatalog() {
|
|
|
95
98
|
...switchClassCatalog(),
|
|
96
99
|
...dateFieldClassCatalog(),
|
|
97
100
|
...calendarButtonClassCatalog(),
|
|
101
|
+
...dateRangeClassCatalog(),
|
|
102
|
+
...listClassCatalog(),
|
|
103
|
+
...countPillClassCatalog(),
|
|
98
104
|
...checkboxClassCatalog(),
|
|
99
105
|
...radioGroupClassCatalog(),
|
|
100
106
|
...spinnerClassCatalog(),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type DateOnlyRange } from './date-only.js';
|
|
2
|
+
/**
|
|
3
|
+
* A range being picked in DateRangeButton's calendar: the days so far, and
|
|
4
|
+
* whether the next press sets its end. A first press makes a one-day range
|
|
5
|
+
* that waits for its end, so Apply always has a whole range to give.
|
|
6
|
+
*/
|
|
7
|
+
export type RangeDraft = {
|
|
8
|
+
range: DateOnlyRange;
|
|
9
|
+
awaitingEnd: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** A whole range, such as the value the calendar opens on. */
|
|
12
|
+
export declare function settledDraft(range: DateOnlyRange): RangeDraft;
|
|
13
|
+
/**
|
|
14
|
+
* The draft after a press on `day`: the first press sets the start, the
|
|
15
|
+
* second the end. A second press before the start starts over from that
|
|
16
|
+
* day, and a press on a whole range starts a new one.
|
|
17
|
+
*/
|
|
18
|
+
export declare function pressDay(draft: RangeDraft, day: string): RangeDraft;
|
|
19
|
+
/** The days in a range, both ends counted: 1 for a one-day range. */
|
|
20
|
+
export declare function rangeDayCount(range: DateOnlyRange): number;
|
|
21
|
+
/**
|
|
22
|
+
* The last day a press may pick: `max`, or while the draft waits for its
|
|
23
|
+
* end, the day `maxDays` long from its start when that is earlier.
|
|
24
|
+
*/
|
|
25
|
+
export declare function latestPick(draft: RangeDraft, max: string | undefined, maxDays: number | undefined): string | undefined;
|
|
26
|
+
export declare function sameRange(a: DateOnlyRange, b: DateOnlyRange): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { addDays, daysBetween } from './calendar-month.js';
|
|
2
|
+
import { clampDateOnly } from './date-only.js';
|
|
3
|
+
/** A whole range, such as the value the calendar opens on. */
|
|
4
|
+
export function settledDraft(range) {
|
|
5
|
+
return { range, awaitingEnd: false };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The draft after a press on `day`: the first press sets the start, the
|
|
9
|
+
* second the end. A second press before the start starts over from that
|
|
10
|
+
* day, and a press on a whole range starts a new one.
|
|
11
|
+
*/
|
|
12
|
+
export function pressDay(draft, day) {
|
|
13
|
+
if (draft.awaitingEnd && day >= draft.range.start)
|
|
14
|
+
return {
|
|
15
|
+
range: { start: draft.range.start, end: day },
|
|
16
|
+
awaitingEnd: false,
|
|
17
|
+
};
|
|
18
|
+
return { range: { start: day, end: day }, awaitingEnd: true };
|
|
19
|
+
}
|
|
20
|
+
/** The days in a range, both ends counted: 1 for a one-day range. */
|
|
21
|
+
export function rangeDayCount(range) {
|
|
22
|
+
return daysBetween(range.start, range.end) + 1;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The last day a press may pick: `max`, or while the draft waits for its
|
|
26
|
+
* end, the day `maxDays` long from its start when that is earlier.
|
|
27
|
+
*/
|
|
28
|
+
export function latestPick(draft, max, maxDays) {
|
|
29
|
+
if (!draft.awaitingEnd || maxDays === undefined)
|
|
30
|
+
return max;
|
|
31
|
+
const longest = clampDateOnly(addDays(draft.range.start, maxDays - 1));
|
|
32
|
+
return max === undefined || longest < max ? longest : max;
|
|
33
|
+
}
|
|
34
|
+
export function sameRange(a, b) {
|
|
35
|
+
return a.start === b.start && a.end === b.end;
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type DateOnlyRange } from './date-only.js';
|
|
2
|
+
/** A named range a person can choose in one press, such as "Last 7 days". */
|
|
3
|
+
export type DateRangePreset = {
|
|
4
|
+
/** Unique among the presets; never shown. */
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
range: DateOnlyRange;
|
|
8
|
+
};
|
|
9
|
+
export type DateRangeInput = {
|
|
10
|
+
label: string;
|
|
11
|
+
value: DateOnlyRange;
|
|
12
|
+
presets: readonly DateRangePreset[];
|
|
13
|
+
min?: string;
|
|
14
|
+
max?: string;
|
|
15
|
+
maxDays?: number;
|
|
16
|
+
today?: string;
|
|
17
|
+
weekStartsOn: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Fails closed on DateRangeButton props it cannot draw: empty words, a
|
|
21
|
+
* malformed or reversed value, bounds or today, a `maxDays` that is not a
|
|
22
|
+
* whole number from 1, and presets that repeat an id or that a press could
|
|
23
|
+
* not pick (outside the bounds or longer than `maxDays`). A value outside
|
|
24
|
+
* the bounds stays the value, as on CalendarButton.
|
|
25
|
+
*/
|
|
26
|
+
export declare function assertDateRangeInput(input: DateRangeInput): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assertWeekStart } from './calendar-month.js';
|
|
2
|
+
import { rangeDayCount } from './date-range-draft.js';
|
|
3
|
+
import { assertDateOnly, assertDateRange, isOutsideDateRange, } from './date-only.js';
|
|
4
|
+
function isBlank(text) {
|
|
5
|
+
return typeof text !== 'string' || text.trim() === '';
|
|
6
|
+
}
|
|
7
|
+
function assertValue(value) {
|
|
8
|
+
if (typeof value !== 'object' || value === null)
|
|
9
|
+
throw new RangeError('value must be a { start, end } range');
|
|
10
|
+
assertDateOnly('value.start', value.start);
|
|
11
|
+
assertDateOnly('value.end', value.end);
|
|
12
|
+
if (value.start > value.end)
|
|
13
|
+
throw new RangeError('value.start must not be after value.end');
|
|
14
|
+
}
|
|
15
|
+
function assertBounds(min, max) {
|
|
16
|
+
if (min !== undefined)
|
|
17
|
+
assertDateOnly('min', min);
|
|
18
|
+
if (max !== undefined)
|
|
19
|
+
assertDateOnly('max', max);
|
|
20
|
+
if (min !== undefined && max !== undefined && min > max)
|
|
21
|
+
throw new RangeError('min must not be after max');
|
|
22
|
+
}
|
|
23
|
+
function assertPreset(preset, { min, max, maxDays }) {
|
|
24
|
+
if (isBlank(preset.id) || isBlank(preset.label))
|
|
25
|
+
throw new RangeError('presets must have non-empty ids and labels');
|
|
26
|
+
assertDateRange(preset.range);
|
|
27
|
+
const { start, end } = preset.range;
|
|
28
|
+
if (isOutsideDateRange(start, min, max) || isOutsideDateRange(end, min, max))
|
|
29
|
+
throw new RangeError(`preset ${preset.id} must lie inside min and max`);
|
|
30
|
+
if (maxDays !== undefined && rangeDayCount(preset.range) > maxDays)
|
|
31
|
+
throw new RangeError(`preset ${preset.id} must not exceed maxDays`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fails closed on DateRangeButton props it cannot draw: empty words, a
|
|
35
|
+
* malformed or reversed value, bounds or today, a `maxDays` that is not a
|
|
36
|
+
* whole number from 1, and presets that repeat an id or that a press could
|
|
37
|
+
* not pick (outside the bounds or longer than `maxDays`). A value outside
|
|
38
|
+
* the bounds stays the value, as on CalendarButton.
|
|
39
|
+
*/
|
|
40
|
+
export function assertDateRangeInput(input) {
|
|
41
|
+
if (isBlank(input.label))
|
|
42
|
+
throw new RangeError('label must be non-empty text');
|
|
43
|
+
assertValue(input.value);
|
|
44
|
+
assertBounds(input.min, input.max);
|
|
45
|
+
if (input.today !== undefined)
|
|
46
|
+
assertDateOnly('today', input.today);
|
|
47
|
+
assertWeekStart(input.weekStartsOn);
|
|
48
|
+
const { maxDays } = input;
|
|
49
|
+
if (maxDays !== undefined && (!Number.isInteger(maxDays) || maxDays < 1))
|
|
50
|
+
throw new RangeError('maxDays must be a whole number from 1');
|
|
51
|
+
const ids = new Set();
|
|
52
|
+
for (const preset of input.presets) {
|
|
53
|
+
assertPreset(preset, input);
|
|
54
|
+
if (ids.has(preset.id))
|
|
55
|
+
throw new RangeError(`presets must have unique ids: ${preset.id}`);
|
|
56
|
+
ids.add(preset.id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CalendarButtonLabels } from './calendar-button-labels.js';
|
|
2
|
+
/**
|
|
3
|
+
* DateRangeButton's own words. Products pass their translations; month and
|
|
4
|
+
* weekday names, and the dates themselves, come from `Intl` instead.
|
|
5
|
+
*/
|
|
6
|
+
export type DateRangeButtonLabels = Pick<CalendarButtonLabels, 'previousMonth' | 'nextMonth' | 'nameSeparator'> & {
|
|
7
|
+
/** Names the list of presets, such as "Quick ranges". */
|
|
8
|
+
presets: string;
|
|
9
|
+
/** Applies the chosen range and closes the calendar, such as "Apply". */
|
|
10
|
+
apply: string;
|
|
11
|
+
/** Closes the calendar and keeps the range it opened with. */
|
|
12
|
+
cancel: string;
|
|
13
|
+
/** The bottom sheet's close button. */
|
|
14
|
+
close: string;
|
|
15
|
+
/** How to pick, shown under the calendar, such as "Choose a first and a last day." */
|
|
16
|
+
hint: string;
|
|
17
|
+
/** The length of the chosen range, such as "7 days". */
|
|
18
|
+
dayCount: (days: number) => string;
|
|
19
|
+
};
|
|
20
|
+
export declare const defaultDateRangeButtonLabels: DateRangeButtonLabels;
|
|
21
|
+
/** Consumer words over the English defaults; empty words fail closed. */
|
|
22
|
+
export declare function resolveDateRangeButtonLabels(labels?: Partial<DateRangeButtonLabels>): DateRangeButtonLabels;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defaultNameSeparator, } from './calendar-button-labels.js';
|
|
2
|
+
export const defaultDateRangeButtonLabels = {
|
|
3
|
+
previousMonth: 'Previous month',
|
|
4
|
+
nextMonth: 'Next month',
|
|
5
|
+
nameSeparator: defaultNameSeparator,
|
|
6
|
+
presets: 'Presets',
|
|
7
|
+
apply: 'Apply',
|
|
8
|
+
cancel: 'Cancel',
|
|
9
|
+
close: 'Close',
|
|
10
|
+
hint: 'Choose a first and a last day.',
|
|
11
|
+
dayCount: (days) => (days === 1 ? '1 day' : `${days} days`),
|
|
12
|
+
};
|
|
13
|
+
/** Consumer words over the English defaults; empty words fail closed. */
|
|
14
|
+
export function resolveDateRangeButtonLabels(labels = {}) {
|
|
15
|
+
const resolved = { ...defaultDateRangeButtonLabels, ...labels };
|
|
16
|
+
const { dayCount, nameSeparator, ...words } = resolved;
|
|
17
|
+
for (const [key, text] of Object.entries(words))
|
|
18
|
+
if (typeof text !== 'string' || text.trim() === '')
|
|
19
|
+
throw new RangeError(`labels.${key} must be non-empty text`);
|
|
20
|
+
// Only an empty string is refused: a lone space is a valid separator.
|
|
21
|
+
if (typeof nameSeparator !== 'string' || nameSeparator === '')
|
|
22
|
+
throw new RangeError('labels.nameSeparator must be non-empty text');
|
|
23
|
+
if (typeof dayCount !== 'function')
|
|
24
|
+
throw new RangeError('labels.dayCount must be a function');
|
|
25
|
+
return resolved;
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CalendarMonth } from './calendar-month.js';
|
|
2
|
+
/** How many months a range calendar shows: two side by side, or one. */
|
|
3
|
+
export type RangeMonthCount = 1 | 2;
|
|
4
|
+
/**
|
|
5
|
+
* The first month a range calendar shows so that `focusDate` is in view.
|
|
6
|
+
* A view that already holds it stays; a focus date before it becomes the
|
|
7
|
+
* first month, and one after it the last. With no view yet, the focus
|
|
8
|
+
* date's month is the last one shown, but never a month before `min`'s.
|
|
9
|
+
*/
|
|
10
|
+
export declare function firstShownMonth(focusDate: string, count: RangeMonthCount, shown: CalendarMonth | undefined, min: string | undefined): CalendarMonth;
|
|
11
|
+
/** The months shown from `first`, in order. */
|
|
12
|
+
export declare function shownMonths(first: CalendarMonth, count: RangeMonthCount): CalendarMonth[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { monthOf, monthsBetween, shiftMonth, } from './calendar-month.js';
|
|
2
|
+
/**
|
|
3
|
+
* The first month a range calendar shows so that `focusDate` is in view.
|
|
4
|
+
* A view that already holds it stays; a focus date before it becomes the
|
|
5
|
+
* first month, and one after it the last. With no view yet, the focus
|
|
6
|
+
* date's month is the last one shown, but never a month before `min`'s.
|
|
7
|
+
*/
|
|
8
|
+
export function firstShownMonth(focusDate, count, shown, min) {
|
|
9
|
+
const focus = monthOf(focusDate);
|
|
10
|
+
if (shown !== undefined) {
|
|
11
|
+
const offset = monthsBetween(shown, focus);
|
|
12
|
+
if (offset >= 0 && offset < count)
|
|
13
|
+
return shown;
|
|
14
|
+
if (offset < 0)
|
|
15
|
+
return focus;
|
|
16
|
+
return shiftMonth(focus, 1 - count);
|
|
17
|
+
}
|
|
18
|
+
const first = shiftMonth(focus, 1 - count);
|
|
19
|
+
// No month before 0001 exists: the first year's January leads.
|
|
20
|
+
if (first.year < 1)
|
|
21
|
+
return focus;
|
|
22
|
+
if (min !== undefined && monthsBetween(first, monthOf(min)) > 0)
|
|
23
|
+
return monthOf(min);
|
|
24
|
+
return first;
|
|
25
|
+
}
|
|
26
|
+
/** The months shown from `first`, in order. */
|
|
27
|
+
export function shownMonths(first, count) {
|
|
28
|
+
return Array.from({ length: count }, (_, index) => shiftMonth(first, index));
|
|
29
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type DateOnlyRange } from './date-only.js';
|
|
2
|
+
/**
|
|
3
|
+
* A range as the button shows it, the locale sharing what both ends have,
|
|
4
|
+
* such as "Oct 6 – 12, 2026", or "Oct 6, 2026" for one day.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatRangeShort(range: DateOnlyRange, locale: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* A range with its weekdays, as the calendar states the one being picked,
|
|
9
|
+
* such as "Tue, Oct 6 – Mon, Oct 12, 2026".
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatRangeLong(range: DateOnlyRange, locale: string): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { dateFormatter, formatterDate } from './date-formatters.js';
|
|
2
|
+
import { parseDateOnly } from './date-only.js';
|
|
3
|
+
const shortRange = {
|
|
4
|
+
month: 'short',
|
|
5
|
+
day: 'numeric',
|
|
6
|
+
year: 'numeric',
|
|
7
|
+
};
|
|
8
|
+
const longRange = {
|
|
9
|
+
weekday: 'short',
|
|
10
|
+
month: 'short',
|
|
11
|
+
day: 'numeric',
|
|
12
|
+
year: 'numeric',
|
|
13
|
+
};
|
|
14
|
+
function formatRange(range, locale, options) {
|
|
15
|
+
const start = parseDateOnly(range.start);
|
|
16
|
+
const end = parseDateOnly(range.end);
|
|
17
|
+
if (!start || !end)
|
|
18
|
+
return '';
|
|
19
|
+
const format = dateFormatter(locale, options);
|
|
20
|
+
// Browsers before formatRange (Safari 14.0) write both ends in full.
|
|
21
|
+
if (typeof format.formatRange !== 'function')
|
|
22
|
+
return range.start === range.end
|
|
23
|
+
? format.format(formatterDate(start))
|
|
24
|
+
: `${format.format(formatterDate(start))} – ${format.format(formatterDate(end))}`;
|
|
25
|
+
return format.formatRange(formatterDate(start), formatterDate(end));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A range as the button shows it, the locale sharing what both ends have,
|
|
29
|
+
* such as "Oct 6 – 12, 2026", or "Oct 6, 2026" for one day.
|
|
30
|
+
*/
|
|
31
|
+
export function formatRangeShort(range, locale) {
|
|
32
|
+
return formatRange(range, locale, shortRange);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A range with its weekdays, as the calendar states the one being picked,
|
|
36
|
+
* such as "Tue, Oct 6 – Mon, Oct 12, 2026".
|
|
37
|
+
*/
|
|
38
|
+
export function formatRangeLong(range, locale) {
|
|
39
|
+
return formatRange(range, locale, longRange);
|
|
40
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export type { AccordionProps, AccordionSize } from './components/Accordion.js';
|
|
|
5
5
|
export { Switch, type SwitchProps } from './components/Switch.js';
|
|
6
6
|
export { DateField, type DateFieldLabels, type DateFieldProps, type WeekStart, } from './components/DateField.js';
|
|
7
7
|
export { CalendarButton, type CalendarButtonLabels, type CalendarButtonProps, type CalendarButtonRange, } from './components/CalendarButton.js';
|
|
8
|
+
export { DateRangeButton, type DateRangeButtonLabels, type DateRangeButtonProps, type DateRangeButtonValue, type DateRangePreset, } from './components/DateRangeButton.js';
|
|
9
|
+
export { ListGroup, type ListGroupProps } from './components/ListGroup.js';
|
|
10
|
+
export { ListRow, type ListRowProps } from './components/ListRow.js';
|
|
11
|
+
export { listGroupVariants, listRowTitleAppearances, listRowTones, listRowValueTones, type ListGroupMaxHeight, type ListGroupVariant, type ListRowTitleAppearance, type ListRowTone, type ListRowValueTone, } from './list-options.js';
|
|
8
12
|
export { Checkbox, type CheckboxProps } from './components/Checkbox.js';
|
|
9
13
|
export { Spinner, type SpinnerProps, type SpinnerSize, } from './components/Spinner.js';
|
|
10
14
|
export { GlyphTile, glyphTileSizes, type GlyphTileHue, type GlyphTileProps, type GlyphTileSize, } from './components/GlyphTile.js';
|
|
@@ -28,7 +32,7 @@ export { boxLinkAppearances } from './css/css-box-link.js';
|
|
|
28
32
|
export { ButtonGroup, buttonGroupAppearances, type ButtonGroupAppearance, type ButtonGroupJustify, type ButtonGroupProps, } from './components/ButtonGroup.js';
|
|
29
33
|
export { Card, type CardProps, type CardVariant } from './components/Card.js';
|
|
30
34
|
export { Dialog, dialogAppearances, dialogSizes, type DialogAppearance, type DialogProps, type DialogRole, type DialogSize, type DialogTitleLevel, } from './components/Dialog.js';
|
|
31
|
-
export { StatTile, type StatTileEmphasis, type StatTileProps, type StatTileTone, statTileTones, } from './components/StatTile.js';
|
|
35
|
+
export { StatTile, type StatTileAppearance, statTileAppearances, type StatTileEmphasis, type StatTileProps, type StatTileTone, statTileTones, } from './components/StatTile.js';
|
|
32
36
|
export { TabPanel, type TabItem, type TabPanelProps, Tabs, type TabsProps, tabId, tabPanelId, } from './components/Tabs.js';
|
|
33
37
|
export { Toast, toastPlacements, toastTones, type ToastAction, type ToastPlacement, } from './components/Toast.js';
|
|
34
38
|
export type { ToastProps, ToastTone } from './components/Toast.js';
|
|
@@ -40,6 +44,7 @@ export { Inline, type InlineProps } from './components/Inline.js';
|
|
|
40
44
|
export { Split } from './components/Split.js';
|
|
41
45
|
export type { SplitProps } from './components/Split.js';
|
|
42
46
|
export { Nav, navAppearances, type NavAppearance, type NavProps, } from './components/Nav.js';
|
|
47
|
+
export { NavCount, type NavCountProps } from './components/NavCount.js';
|
|
43
48
|
export { SectionNav, sectionNavAppearances, type SectionNavAppearance, type SectionNavItem, type SectionNavProps, } from './components/SectionNav.js';
|
|
44
49
|
export { SegmentedControl, type SegmentedControlProps, type SegmentedControlVariant, type SegmentedItem, } from './components/SegmentedControl.js';
|
|
45
50
|
export { Combobox, comboboxSizes, type ComboboxAction, type ComboboxBadge, type ComboboxGroup, type ComboboxOption, type ComboboxProps, type ComboboxSize, } from './components/Combobox.js';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,10 @@ export { Accordion } from './components/Accordion.js';
|
|
|
4
4
|
export { Switch } from './components/Switch.js';
|
|
5
5
|
export { DateField, } from './components/DateField.js';
|
|
6
6
|
export { CalendarButton, } from './components/CalendarButton.js';
|
|
7
|
+
export { DateRangeButton, } from './components/DateRangeButton.js';
|
|
8
|
+
export { ListGroup } from './components/ListGroup.js';
|
|
9
|
+
export { ListRow } from './components/ListRow.js';
|
|
10
|
+
export { listGroupVariants, listRowTitleAppearances, listRowTones, listRowValueTones, } from './list-options.js';
|
|
7
11
|
export { Checkbox } from './components/Checkbox.js';
|
|
8
12
|
export { Spinner, } from './components/Spinner.js';
|
|
9
13
|
export { GlyphTile, glyphTileSizes, } from './components/GlyphTile.js';
|
|
@@ -27,7 +31,7 @@ export { boxLinkAppearances } from './css/css-box-link.js';
|
|
|
27
31
|
export { ButtonGroup, buttonGroupAppearances, } from './components/ButtonGroup.js';
|
|
28
32
|
export { Card } from './components/Card.js';
|
|
29
33
|
export { Dialog, dialogAppearances, dialogSizes, } from './components/Dialog.js';
|
|
30
|
-
export { StatTile, statTileTones, } from './components/StatTile.js';
|
|
34
|
+
export { StatTile, statTileAppearances, statTileTones, } from './components/StatTile.js';
|
|
31
35
|
export { TabPanel, Tabs, tabId, tabPanelId, } from './components/Tabs.js';
|
|
32
36
|
export { Toast, toastPlacements, toastTones, } from './components/Toast.js';
|
|
33
37
|
export { Field, fieldVariants, } from './components/Field.js';
|
|
@@ -37,6 +41,7 @@ export { Stepper, stepperLimitAppearances, } from './components/Stepper.js';
|
|
|
37
41
|
export { Inline } from './components/Inline.js';
|
|
38
42
|
export { Split } from './components/Split.js';
|
|
39
43
|
export { Nav, navAppearances, } from './components/Nav.js';
|
|
44
|
+
export { NavCount } from './components/NavCount.js';
|
|
40
45
|
export { SectionNav, sectionNavAppearances, } from './components/SectionNav.js';
|
|
41
46
|
export { SegmentedControl, } from './components/SegmentedControl.js';
|
|
42
47
|
export { Combobox, comboboxSizes, } from './components/Combobox.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
import { type TableCellTone } from './css/css-table-ledger.js';
|
|
3
|
+
/**
|
|
4
|
+
* `panel` (the default) is one bordered panel on the surface; `plain`
|
|
5
|
+
* draws no panel and a hairline above the first row too, for rows inside
|
|
6
|
+
* a surface that already has one, such as a Card.
|
|
7
|
+
*/
|
|
8
|
+
export declare const listGroupVariants: readonly ["panel", "plain"];
|
|
9
|
+
export type ListGroupVariant = (typeof listGroupVariants)[number];
|
|
10
|
+
/**
|
|
11
|
+
* A list's bounded height, counted in rows: as tall as `rows` rows of a
|
|
12
|
+
* title and one detail line, the rest scrolling inside it. Taller rows
|
|
13
|
+
* show fewer at a time, shorter ones more.
|
|
14
|
+
*/
|
|
15
|
+
export type ListGroupMaxHeight = {
|
|
16
|
+
rows: number;
|
|
17
|
+
};
|
|
18
|
+
/** `danger` draws the title in the danger colour, for a row that ends something. */
|
|
19
|
+
export declare const listRowTones: readonly ["default", "danger"];
|
|
20
|
+
export type ListRowTone = (typeof listRowTones)[number];
|
|
21
|
+
/** `strong` (the default) sets the title at the label weight; `plain` at the body's. */
|
|
22
|
+
export declare const listRowTitleAppearances: readonly ["strong", "plain"];
|
|
23
|
+
export type ListRowTitleAppearance = (typeof listRowTitleAppearances)[number];
|
|
24
|
+
/** The colours a row's value can take, as a table cell's. */
|
|
25
|
+
export declare const listRowValueTones: readonly ["success", "danger", "muted", "accent"];
|
|
26
|
+
export type ListRowValueTone = TableCellTone;
|
|
27
|
+
/** What ends a row: a chevron (it opens something), a check (chosen), or none. */
|
|
28
|
+
export type ListRowAccessory = 'chevron' | 'check' | 'none';
|
|
29
|
+
export type ListGroupInput = {
|
|
30
|
+
label: string | undefined;
|
|
31
|
+
labelledBy: string | undefined;
|
|
32
|
+
variant: unknown;
|
|
33
|
+
maxHeight: ListGroupMaxHeight | undefined;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Fails closed on a ListGroup it cannot draw: an unknown variant, a
|
|
37
|
+
* `maxHeight` that is not a whole row count from 1, or a bounded list with
|
|
38
|
+
* no name (its scroll region is a keyboard stop and must say what it holds).
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertListGroup({ label, labelledBy, variant, maxHeight, }: ListGroupInput): void;
|
|
41
|
+
/** The row count the generated `sw-list-group-scroll` height reads. */
|
|
42
|
+
export declare function listMaxHeightStyle(maxHeight: ListGroupMaxHeight): CSSProperties;
|
|
43
|
+
export type ListRowInput = {
|
|
44
|
+
title: string;
|
|
45
|
+
tone: unknown;
|
|
46
|
+
titleAppearance: unknown;
|
|
47
|
+
valueTone: unknown;
|
|
48
|
+
accessory: unknown;
|
|
49
|
+
value: string | undefined;
|
|
50
|
+
valueDetail: string | undefined;
|
|
51
|
+
selected: boolean | undefined;
|
|
52
|
+
pressable: boolean;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Fails closed on a ListRow it cannot draw: an empty title or value, an
|
|
56
|
+
* unknown tone, title appearance, value tone or accessory, a `valueDetail`
|
|
57
|
+
* or `valueTone` with no value, or `selected` on a row that is not a
|
|
58
|
+
* button or link (its check would be a state nothing can read).
|
|
59
|
+
*/
|
|
60
|
+
export declare function assertListRow(input: ListRowInput): void;
|
|
61
|
+
/**
|
|
62
|
+
* The row's end: a check while `selected`, nothing for an unchosen choice,
|
|
63
|
+
* a chevron on a row that opens something (unless `accessory` is `none`),
|
|
64
|
+
* and nothing on a read-only row.
|
|
65
|
+
*/
|
|
66
|
+
export declare function listRowAccessory(options: {
|
|
67
|
+
pressable: boolean;
|
|
68
|
+
selected: boolean | undefined;
|
|
69
|
+
accessory: 'chevron' | 'none';
|
|
70
|
+
}): ListRowAccessory;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { tableCellTones } from './css/css-table-ledger.js';
|
|
2
|
+
import { isOneOf } from './is-one-of.js';
|
|
3
|
+
/**
|
|
4
|
+
* `panel` (the default) is one bordered panel on the surface; `plain`
|
|
5
|
+
* draws no panel and a hairline above the first row too, for rows inside
|
|
6
|
+
* a surface that already has one, such as a Card.
|
|
7
|
+
*/
|
|
8
|
+
export const listGroupVariants = ['panel', 'plain'];
|
|
9
|
+
/** `danger` draws the title in the danger colour, for a row that ends something. */
|
|
10
|
+
export const listRowTones = ['default', 'danger'];
|
|
11
|
+
/** `strong` (the default) sets the title at the label weight; `plain` at the body's. */
|
|
12
|
+
export const listRowTitleAppearances = ['strong', 'plain'];
|
|
13
|
+
/** The colours a row's value can take, as a table cell's. */
|
|
14
|
+
export const listRowValueTones = tableCellTones;
|
|
15
|
+
function isBlank(text) {
|
|
16
|
+
return typeof text !== 'string' || text.trim() === '';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fails closed on a ListGroup it cannot draw: an unknown variant, a
|
|
20
|
+
* `maxHeight` that is not a whole row count from 1, or a bounded list with
|
|
21
|
+
* no name (its scroll region is a keyboard stop and must say what it holds).
|
|
22
|
+
*/
|
|
23
|
+
export function assertListGroup({ label, labelledBy, variant, maxHeight, }) {
|
|
24
|
+
if (!isOneOf(listGroupVariants, variant))
|
|
25
|
+
throw new RangeError(`Unknown ListGroup variant: ${String(variant)}`);
|
|
26
|
+
if (maxHeight === undefined)
|
|
27
|
+
return;
|
|
28
|
+
const rows = typeof maxHeight === 'object' && maxHeight !== null
|
|
29
|
+
? maxHeight.rows
|
|
30
|
+
: undefined;
|
|
31
|
+
if (typeof rows !== 'number' || !Number.isInteger(rows) || rows < 1)
|
|
32
|
+
throw new RangeError('ListGroup maxHeight must be { rows } with a whole number from 1');
|
|
33
|
+
if (isBlank(label) && isBlank(labelledBy))
|
|
34
|
+
throw new RangeError('A ListGroup with maxHeight needs aria-label or aria-labelledby');
|
|
35
|
+
}
|
|
36
|
+
/** The row count the generated `sw-list-group-scroll` height reads. */
|
|
37
|
+
export function listMaxHeightStyle(maxHeight) {
|
|
38
|
+
return { '--sw-list-max-rows': maxHeight.rows };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Fails closed on a ListRow it cannot draw: an empty title or value, an
|
|
42
|
+
* unknown tone, title appearance, value tone or accessory, a `valueDetail`
|
|
43
|
+
* or `valueTone` with no value, or `selected` on a row that is not a
|
|
44
|
+
* button or link (its check would be a state nothing can read).
|
|
45
|
+
*/
|
|
46
|
+
export function assertListRow(input) {
|
|
47
|
+
if (isBlank(input.title))
|
|
48
|
+
throw new RangeError('ListRow title must be non-empty text');
|
|
49
|
+
if (!isOneOf(listRowTones, input.tone))
|
|
50
|
+
throw new RangeError(`Unknown ListRow tone: ${String(input.tone)}`);
|
|
51
|
+
if (!isOneOf(listRowTitleAppearances, input.titleAppearance))
|
|
52
|
+
throw new RangeError(`Unknown ListRow titleAppearance: ${String(input.titleAppearance)}`);
|
|
53
|
+
if (input.valueTone !== undefined &&
|
|
54
|
+
!isOneOf(listRowValueTones, input.valueTone))
|
|
55
|
+
throw new RangeError(`Unknown ListRow valueTone: ${String(input.valueTone)}`);
|
|
56
|
+
if (!isOneOf(['chevron', 'none'], input.accessory))
|
|
57
|
+
throw new RangeError(`Unknown ListRow accessory: ${String(input.accessory)}`);
|
|
58
|
+
if (input.value !== undefined && isBlank(input.value))
|
|
59
|
+
throw new RangeError('ListRow value must be non-empty text');
|
|
60
|
+
if (input.selected !== undefined && !input.pressable)
|
|
61
|
+
throw new RangeError('A ListRow choice (selected) needs onPress or href to say its state');
|
|
62
|
+
if (input.value === undefined &&
|
|
63
|
+
(input.valueDetail !== undefined || input.valueTone !== undefined))
|
|
64
|
+
throw new RangeError('ListRow valueDetail and valueTone need a value');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The row's end: a check while `selected`, nothing for an unchosen choice,
|
|
68
|
+
* a chevron on a row that opens something (unless `accessory` is `none`),
|
|
69
|
+
* and nothing on a read-only row.
|
|
70
|
+
*/
|
|
71
|
+
export function listRowAccessory(options) {
|
|
72
|
+
if (options.selected !== undefined)
|
|
73
|
+
return options.selected ? 'check' : 'none';
|
|
74
|
+
if (!options.pressable)
|
|
75
|
+
return 'none';
|
|
76
|
+
return options.accessory;
|
|
77
|
+
}
|