@fusion.dev/helix-core 0.142.1 → 0.144.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/custom-elements.json +10436 -3804
- package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +65 -0
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +77 -16
- package/dist/controllers/arrival-controller/arrival-controller.d.ts +12 -0
- package/dist/controllers/arrival-controller/arrival-controller.js +10 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +99 -0
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +107 -2
- package/dist/controllers/chart-pointer-controller/reading-step.d.ts +57 -0
- package/dist/controllers/chart-pointer-controller/reading-step.js +45 -0
- package/dist/controllers/copy-controller/copy-controller.d.ts +11 -6
- package/dist/controllers/copy-controller/copy-controller.js +16 -9
- package/dist/controllers/delay-controller/delay-controller.d.ts +50 -0
- package/dist/controllers/delay-controller/delay-controller.js +54 -0
- package/dist/controllers/dismiss-controller/dismiss-controller.js +40 -3
- package/dist/controllers/focus-scope-controller/focus-scope-controller.d.ts +56 -0
- package/dist/controllers/focus-scope-controller/focus-scope-controller.js +71 -0
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +79 -1
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +80 -10
- package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +49 -0
- package/dist/controllers/open-phase-controller/open-phase-controller.js +41 -0
- package/dist/controllers/popover-controller/popover-controller.d.ts +11 -0
- package/dist/controllers/popover-controller/popover-controller.js +6 -0
- package/dist/controllers/source-controller/source-controller.d.ts +38 -0
- package/dist/controllers/source-controller/source-controller.js +71 -2
- package/dist/controllers/status-controller/status-controller.d.ts +47 -0
- package/dist/controllers/status-controller/status-controller.js +34 -0
- package/dist/controllers/swipe-controller/swipe-controller.d.ts +61 -0
- package/dist/controllers/swipe-controller/swipe-controller.js +106 -0
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +16 -0
- package/dist/controllers/virtualize-controller/virtualize-controller.js +24 -2
- package/dist/events/events.d.ts +137 -4
- package/dist/events/events.js +96 -0
- package/dist/helix-element/react-bridge.js +6 -4
- package/dist/helix-form-element/helix-form-element.d.ts +131 -2
- package/dist/helix-form-element/helix-form-element.js +216 -6
- package/dist/index.d.ts +30 -9
- package/dist/index.js +29 -8
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +15 -2
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +9 -1
- package/dist/mixins/mixin-collection/mixin-collection.d.ts +4 -2
- package/dist/mixins/mixin-collection/mixin-collection.js +8 -6
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +10 -0
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +9 -0
- package/dist/mixins/mixin-controller-open/mixin-controller-open.js +19 -3
- package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +2 -0
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +43 -0
- package/dist/mixins/mixin-field/mixin-field.d.ts +2 -0
- package/dist/mixins/mixin-field/mixin-field.js +46 -8
- package/dist/mixins/mixin-picker-editing/mixin-picker-editing.d.ts +23 -8
- package/dist/mixins/mixin-picker-editing/mixin-picker-editing.js +25 -1
- package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +29 -5
- package/dist/mixins/mixin-picker-field/mixin-picker-field.js +94 -1
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +67 -0
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +243 -0
- package/dist/utils/arrow/arrow.d.ts +15 -0
- package/dist/utils/arrow/arrow.js +2 -0
- package/dist/utils/audit-name/audit-name.d.ts +28 -0
- package/dist/utils/audit-name/audit-name.js +95 -0
- package/dist/utils/calendar/calendar.d.ts +117 -0
- package/dist/utils/calendar/calendar.js +96 -0
- package/dist/utils/chord/chord.d.ts +43 -0
- package/dist/utils/chord/chord.js +53 -0
- package/dist/utils/clock/clock.d.ts +87 -0
- package/dist/utils/clock/clock.js +68 -0
- package/dist/utils/color-name/color-name.d.ts +28 -0
- package/dist/utils/color-name/color-name.js +150 -0
- package/dist/utils/count-label/count-label.d.ts +13 -0
- package/dist/utils/count-label/count-label.js +5 -0
- package/dist/utils/css-color/css-color.d.ts +12 -0
- package/dist/utils/css-color/css-color.js +2 -2
- package/dist/utils/date-segments/date-segments.d.ts +122 -0
- package/dist/utils/date-segments/date-segments.js +206 -0
- package/dist/utils/draw-dismiss/draw-dismiss.d.ts +30 -0
- package/dist/utils/draw-dismiss/draw-dismiss.js +16 -0
- package/dist/utils/draw-tooltip/draw-tooltip.d.ts +15 -18
- package/dist/utils/draw-tooltip/draw-tooltip.js +16 -5
- package/dist/utils/draw-tooltip/spoken-reading.d.ts +10 -0
- package/dist/utils/draw-tooltip/spoken-reading.js +10 -0
- package/dist/utils/follow-link/follow-link.d.ts +46 -0
- package/dist/utils/follow-link/follow-link.js +37 -0
- package/dist/utils/heading-in/heading-in.d.ts +19 -0
- package/dist/utils/heading-in/heading-in.js +14 -0
- package/dist/utils/number-format/number-format.d.ts +23 -0
- package/dist/utils/number-format/number-format.js +55 -0
- package/dist/utils/platform/platform.d.ts +23 -0
- package/dist/utils/platform/platform.js +11 -0
- package/dist/utils/read-color/read-color.d.ts +14 -0
- package/dist/utils/read-color/read-color.js +13 -0
- package/dist/utils/read-series/read-series.d.ts +34 -0
- package/dist/utils/read-series/read-series.js +32 -4
- package/dist/utils/reflect-elements/reflect-elements.d.ts +37 -0
- package/dist/utils/reflect-elements/reflect-elements.js +40 -0
- package/dist/utils/relative-time/relative-time.d.ts +13 -1
- package/dist/utils/relative-time/relative-time.js +7 -4
- package/dist/utils/report-runtime/report-runtime.d.ts +12 -0
- package/dist/utils/roving-stop/roving-stop.d.ts +20 -0
- package/dist/utils/roving-stop/roving-stop.js +3 -0
- package/dist/utils/scroll-lock/scroll-lock.d.ts +16 -0
- package/dist/utils/scroll-lock/scroll-lock.js +30 -0
- package/dist/utils/swipe/swipe.d.ts +63 -0
- package/dist/utils/swipe/swipe.js +48 -0
- package/dist/utils/tab-stops/tab-stops.d.ts +16 -0
- package/dist/utils/tab-stops/tab-stops.js +22 -0
- package/dist/utils/value-as/value-as.d.ts +20 -0
- package/dist/utils/value-as/value-as.js +16 -0
- package/dist/utils/write-time/write-time.d.ts +11 -1
- package/dist/utils/write-time/write-time.js +7 -7
- package/dist/utils/written-list/written-list.d.ts +29 -2
- package/dist/utils/written-list/written-list.js +11 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const chordParts = (keys) => keys
|
|
2
|
+
.split('+')
|
|
3
|
+
.map((part) => part.trim())
|
|
4
|
+
.filter((part) => part !== '');
|
|
5
|
+
const ARIA = {
|
|
6
|
+
alt: 'Alt',
|
|
7
|
+
backspace: 'Backspace',
|
|
8
|
+
cmd: 'Meta',
|
|
9
|
+
ctrl: 'Control',
|
|
10
|
+
del: 'Delete',
|
|
11
|
+
down: 'ArrowDown',
|
|
12
|
+
enter: 'Enter',
|
|
13
|
+
esc: 'Escape',
|
|
14
|
+
escape: 'Escape',
|
|
15
|
+
left: 'ArrowLeft',
|
|
16
|
+
meta: 'Meta',
|
|
17
|
+
opt: 'Alt',
|
|
18
|
+
right: 'ArrowRight',
|
|
19
|
+
shift: 'Shift',
|
|
20
|
+
space: 'Space',
|
|
21
|
+
tab: 'Tab',
|
|
22
|
+
up: 'ArrowUp',
|
|
23
|
+
};
|
|
24
|
+
export const ariaChord = (keys, apple) => chordParts(keys)
|
|
25
|
+
.map((part) => {
|
|
26
|
+
const name = part.toLowerCase();
|
|
27
|
+
if (name === 'mod') {
|
|
28
|
+
return apple ? 'Meta' : 'Control';
|
|
29
|
+
}
|
|
30
|
+
return ARIA[name] ?? (part.length === 1 ? part.toUpperCase() : part);
|
|
31
|
+
})
|
|
32
|
+
.join('+');
|
|
33
|
+
export const printableChord = (keys) => {
|
|
34
|
+
const parts = chordParts(keys).map((part) => part.toLowerCase());
|
|
35
|
+
const key = parts.at(-1) ?? '';
|
|
36
|
+
return [...key].length === 1 && parts.slice(0, -1).every((modifier) => modifier === 'shift');
|
|
37
|
+
};
|
|
38
|
+
export const matchesChord = (press, keys, apple) => {
|
|
39
|
+
const parts = chordParts(keys).map((part) => part.toLowerCase());
|
|
40
|
+
if (parts.length === 0) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const wanted = parts[parts.length - 1] ?? '';
|
|
44
|
+
const modifiers = new Set(parts.slice(0, -1));
|
|
45
|
+
const ctrl = modifiers.has('ctrl') || (modifiers.has('mod') && !apple);
|
|
46
|
+
const meta = modifiers.has('cmd') || modifiers.has('meta') || (modifiers.has('mod') && apple);
|
|
47
|
+
const symbol = wanted.toLowerCase() === wanted.toUpperCase() && [...wanted].length === 1;
|
|
48
|
+
return (press.key.toLowerCase() === wanted &&
|
|
49
|
+
(modifiers.has('shift') ? press.shiftKey : symbol || !press.shiftKey) &&
|
|
50
|
+
(modifiers.has('alt') || modifiers.has('opt') ? press.altKey : !press.altKey) &&
|
|
51
|
+
(ctrl ? press.ctrlKey : !press.ctrlKey) &&
|
|
52
|
+
(meta ? press.metaKey : !press.metaKey));
|
|
53
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** The two clocks a time is written on: twelve hours, or twenty-four. */
|
|
2
|
+
export type HourCycle = 'h12' | 'h23';
|
|
3
|
+
/**
|
|
4
|
+
* The locale with its clock set, or the locale as given where no clock is.
|
|
5
|
+
*
|
|
6
|
+
* Through the locale's own `-u-hc-` extension, which every `Intl` formatter
|
|
7
|
+
* reads, so a time written, a meridiem named and the question of whether the
|
|
8
|
+
* locale writes twelve hours all answer to the one tag. Empty stays empty
|
|
9
|
+
* without a clock, which is the runtime's own locale.
|
|
10
|
+
*/
|
|
11
|
+
export declare const clockLocale: (locale: string, hourCycle?: HourCycle) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a locale writes times on a twelve hour clock.
|
|
14
|
+
*
|
|
15
|
+
* Asked of `Intl` rather than derived from a list of locales, because it is
|
|
16
|
+
* exactly the question `resolvedOptions` answers and the list is long and
|
|
17
|
+
* changes. `en-US` is true, `en-GB`, `fr-FR` and `ja-JP` are false, measured.
|
|
18
|
+
*/
|
|
19
|
+
export declare const isTwelveHour: (locale?: string) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* What a locale calls the half of the day before or after noon, such as `AM`,
|
|
22
|
+
* `PM`, `오전` or `오후`.
|
|
23
|
+
*
|
|
24
|
+
* Read off `Intl`'s own rendering of a time in that half, which is the one
|
|
25
|
+
* place every engine states it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const dayPeriod: (half: 'AM' | 'PM', locale?: string) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Every time on offer, as seconds since midnight, from `min` to `max` by
|
|
30
|
+
* `step`.
|
|
31
|
+
*
|
|
32
|
+
* `step` is in minutes, or in seconds with `seconds`, the unit
|
|
33
|
+
* `<input type="time">` counts in. Built from `min` upward, so a list that
|
|
34
|
+
* starts at `09:00` steps from nine rather than from midnight.
|
|
35
|
+
*/
|
|
36
|
+
export declare const offeredTimes: (min: string, max: string, step: number, seconds?: boolean) => number[];
|
|
37
|
+
/**
|
|
38
|
+
* The time on offer nearest to one, the earlier winning a tie, or the time
|
|
39
|
+
* itself where nothing is on offer.
|
|
40
|
+
*/
|
|
41
|
+
export declare const nearestTime: (times: readonly number[], at: number) => number;
|
|
42
|
+
/**
|
|
43
|
+
* The parts a wheel shows for a time: the hour as the clock writes it, the
|
|
44
|
+
* minute, the second, and which half of the day.
|
|
45
|
+
*/
|
|
46
|
+
export declare const wheelParts: (at: number, twelve: boolean) => {
|
|
47
|
+
hour: number;
|
|
48
|
+
meridiem: 'AM' | 'PM';
|
|
49
|
+
minute: number;
|
|
50
|
+
second: number;
|
|
51
|
+
shown: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The hour of the day a wheel's hour means: on a twelve hour clock the wheel
|
|
55
|
+
* says 1 to 12 and the meridiem says which half, and 12 AM is midnight.
|
|
56
|
+
*/
|
|
57
|
+
export declare const wholeHour: (shown: number, half?: 'AM' | 'PM') => number;
|
|
58
|
+
/** What a time is checked against: its bounds, the times barred by name, and the times on offer. */
|
|
59
|
+
export interface TimeRules {
|
|
60
|
+
/** Times barred by name, separated by commas or spaces. */
|
|
61
|
+
disableTimes: string;
|
|
62
|
+
/** The latest time, or empty for the end of the day. */
|
|
63
|
+
max: string;
|
|
64
|
+
/** The earliest time, or empty for midnight. */
|
|
65
|
+
min: string;
|
|
66
|
+
/**
|
|
67
|
+
* The times on offer, as seconds since midnight, where a time off them is a
|
|
68
|
+
* fault. Left out, the step is not checked.
|
|
69
|
+
*/
|
|
70
|
+
offered?: readonly number[] | undefined;
|
|
71
|
+
}
|
|
72
|
+
/** What is wrong with a time, as the validity flag a control reports, or `unavailable` for a barred one. */
|
|
73
|
+
export type TimeFault = 'rangeOverflow' | 'rangeUnderflow' | 'stepMismatch' | 'unavailable';
|
|
74
|
+
/**
|
|
75
|
+
* Whether a time, as seconds since midnight, is one `disable-times` names,
|
|
76
|
+
* matching `09:30` and `09:30:00` alike.
|
|
77
|
+
*/
|
|
78
|
+
export declare const timeBarred: (at: number, disableTimes: string) => boolean;
|
|
79
|
+
/**
|
|
80
|
+
* What is wrong with a time a control can read, or `null`: before `min`, past
|
|
81
|
+
* `max`, barred by name, or off the times on offer, asked in that order.
|
|
82
|
+
*
|
|
83
|
+
* Time Picker reports the first fault of each time it holds, and Time Input
|
|
84
|
+
* the first fault any of its times has, so each asks this of every time and
|
|
85
|
+
* keeps the order its own message has always taken.
|
|
86
|
+
*/
|
|
87
|
+
export declare const timeFault: (value: string, rules: TimeRules) => TimeFault | null;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { valueAsSeconds } from '../value-as/value-as.js';
|
|
2
|
+
import { writtenList } from '../written-list/written-list.js';
|
|
3
|
+
const DAY = 86_400;
|
|
4
|
+
export const clockLocale = (locale, hourCycle) => {
|
|
5
|
+
if (!hourCycle) {
|
|
6
|
+
return locale;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const base = new Intl.DateTimeFormat(locale || undefined).resolvedOptions().locale;
|
|
10
|
+
return new Intl.Locale(base, { hourCycle }).toString();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return locale;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export const isTwelveHour = (locale) => new Intl.DateTimeFormat(locale || undefined, { timeStyle: 'short' }).resolvedOptions().hour12 ===
|
|
17
|
+
true;
|
|
18
|
+
export const dayPeriod = (half, locale) => new Intl.DateTimeFormat(locale || undefined, { hour: 'numeric', hour12: true, timeZone: 'UTC' })
|
|
19
|
+
.formatToParts(new Date(Date.UTC(1970, 0, 1, half === 'AM' ? 9 : 21)))
|
|
20
|
+
.find((part) => part.type === 'dayPeriod')?.value ?? half;
|
|
21
|
+
export const offeredTimes = (min, max, step, seconds = false) => {
|
|
22
|
+
const from = valueAsSeconds(min) ?? 0;
|
|
23
|
+
const to = valueAsSeconds(max) ?? (seconds ? DAY - 1 : DAY - 60);
|
|
24
|
+
const every = Math.max(1, Math.round(step)) * (seconds ? 1 : 60);
|
|
25
|
+
const found = [];
|
|
26
|
+
for (let at = from; at <= to; at += every) {
|
|
27
|
+
found.push(at);
|
|
28
|
+
}
|
|
29
|
+
return found;
|
|
30
|
+
};
|
|
31
|
+
export const nearestTime = (times, at) => {
|
|
32
|
+
let best = times[0] ?? at;
|
|
33
|
+
for (const one of times) {
|
|
34
|
+
if (Math.abs(one - at) < Math.abs(best - at)) {
|
|
35
|
+
best = one;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return best;
|
|
39
|
+
};
|
|
40
|
+
export const wheelParts = (at, twelve) => {
|
|
41
|
+
const hour = Math.floor(at / 3600);
|
|
42
|
+
return {
|
|
43
|
+
hour,
|
|
44
|
+
meridiem: hour < 12 ? 'AM' : 'PM',
|
|
45
|
+
minute: Math.floor(at / 60) % 60,
|
|
46
|
+
second: at % 60,
|
|
47
|
+
shown: twelve ? (hour % 12 === 0 ? 12 : hour % 12) : hour,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export const wholeHour = (shown, half) => half === undefined ? shown : (shown % 12) + (half === 'PM' ? 12 : 0);
|
|
51
|
+
export const timeBarred = (at, disableTimes) => writtenList(disableTimes).some((one) => valueAsSeconds(one) === at);
|
|
52
|
+
export const timeFault = (value, rules) => {
|
|
53
|
+
const read = valueAsSeconds(value);
|
|
54
|
+
const at = read ?? 0;
|
|
55
|
+
if (at < (valueAsSeconds(rules.min) ?? 0)) {
|
|
56
|
+
return 'rangeUnderflow';
|
|
57
|
+
}
|
|
58
|
+
if (at > (valueAsSeconds(rules.max) ?? DAY - 1)) {
|
|
59
|
+
return 'rangeOverflow';
|
|
60
|
+
}
|
|
61
|
+
if (read !== null && timeBarred(at, rules.disableTimes)) {
|
|
62
|
+
return 'unavailable';
|
|
63
|
+
}
|
|
64
|
+
if (rules.offered && !rules.offered.includes(at)) {
|
|
65
|
+
return 'stepMismatch';
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Color } from '../css-color/css-color.js';
|
|
2
|
+
/**
|
|
3
|
+
* The words a color's name is made of, keyed by their English, with the two
|
|
4
|
+
* sentences that put them together.
|
|
5
|
+
*
|
|
6
|
+
* **The keys are React Aria's**, the strings `@react-stately/color` ships for
|
|
7
|
+
* `Color.getColorName`, so a page that already translates those can hand the
|
|
8
|
+
* same table over. `colorName` and `transparentColorName` are the sentences,
|
|
9
|
+
* with `{lightness}`, `{chroma}`, `{hue}` and `{percentTransparent}` filled in;
|
|
10
|
+
* every other key is one word or one pair of words. A pair is its own entry,
|
|
11
|
+
* because a language may join two hues in a way that is not two words with a
|
|
12
|
+
* space between them.
|
|
13
|
+
*/
|
|
14
|
+
export type ColorWords = Record<'black' | 'blue' | 'blue purple' | 'brown' | 'brown yellow' | 'colorName' | 'cyan' | 'cyan blue' | 'dark' | 'gray' | 'grayish' | 'green' | 'green cyan' | 'light' | 'magenta' | 'magenta pink' | 'orange' | 'orange yellow' | 'pale' | 'pink' | 'pink red' | 'purple' | 'purple magenta' | 'red' | 'red orange' | 'transparentColorName' | 'very dark' | 'very light' | 'vibrant' | 'white' | 'yellow' | 'yellow green', string>;
|
|
15
|
+
/** The English the names are spoken in where a page gives no words of its own. */
|
|
16
|
+
export declare const COLOR_WORDS: ColorWords;
|
|
17
|
+
/**
|
|
18
|
+
* A color's name, such as "vibrant red" or "dark grayish blue", in the words
|
|
19
|
+
* given.
|
|
20
|
+
*
|
|
21
|
+
* The same reading React Aria's `Color.getColorName` gives, from the same
|
|
22
|
+
* thresholds: a hue from where the color sits on the OKLCH wheel, a lightness
|
|
23
|
+
* from very dark to very light, and a chroma from grayish to vibrant. A color
|
|
24
|
+
* with any transparency says how much, in a percentage written for `locale`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const colorName: (color: Color, given?: Partial<ColorWords>, locale?: string) => string;
|
|
27
|
+
/** The name of a color's hue alone, such as "red orange", in the words given. */
|
|
28
|
+
export declare const hueName: (color: Color, given?: Partial<ColorWords>, locale?: string) => string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const COLOR_WORDS = {
|
|
2
|
+
black: 'black',
|
|
3
|
+
blue: 'blue',
|
|
4
|
+
'blue purple': 'blue purple',
|
|
5
|
+
brown: 'brown',
|
|
6
|
+
'brown yellow': 'brown yellow',
|
|
7
|
+
colorName: '{lightness} {chroma} {hue}',
|
|
8
|
+
cyan: 'cyan',
|
|
9
|
+
'cyan blue': 'cyan blue',
|
|
10
|
+
dark: 'dark',
|
|
11
|
+
gray: 'gray',
|
|
12
|
+
grayish: 'grayish',
|
|
13
|
+
green: 'green',
|
|
14
|
+
'green cyan': 'green cyan',
|
|
15
|
+
light: 'light',
|
|
16
|
+
magenta: 'magenta',
|
|
17
|
+
'magenta pink': 'magenta pink',
|
|
18
|
+
orange: 'orange',
|
|
19
|
+
'orange yellow': 'orange yellow',
|
|
20
|
+
pale: 'pale',
|
|
21
|
+
pink: 'pink',
|
|
22
|
+
'pink red': 'pink red',
|
|
23
|
+
purple: 'purple',
|
|
24
|
+
'purple magenta': 'purple magenta',
|
|
25
|
+
red: 'red',
|
|
26
|
+
'red orange': 'red orange',
|
|
27
|
+
transparentColorName: '{lightness} {chroma} {hue}, {percentTransparent} transparent',
|
|
28
|
+
'very dark': 'very dark',
|
|
29
|
+
'very light': 'very light',
|
|
30
|
+
vibrant: 'vibrant',
|
|
31
|
+
white: 'white',
|
|
32
|
+
yellow: 'yellow',
|
|
33
|
+
'yellow green': 'yellow green',
|
|
34
|
+
};
|
|
35
|
+
const HUES = [
|
|
36
|
+
[0, 'pink'],
|
|
37
|
+
[15, 'red'],
|
|
38
|
+
[48, 'orange'],
|
|
39
|
+
[94, 'yellow'],
|
|
40
|
+
[135, 'green'],
|
|
41
|
+
[175, 'cyan'],
|
|
42
|
+
[264, 'blue'],
|
|
43
|
+
[284, 'purple'],
|
|
44
|
+
[320, 'magenta'],
|
|
45
|
+
[349, 'pink'],
|
|
46
|
+
];
|
|
47
|
+
const BROWN_BELOW = 0.68;
|
|
48
|
+
const YELLOW_GREEN_BELOW = 0.85;
|
|
49
|
+
const DARK_BELOW = 0.55;
|
|
50
|
+
const GRAY_BELOW = 0.001;
|
|
51
|
+
const linear = (channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
52
|
+
const oklch = (color) => {
|
|
53
|
+
const hue = (((color.hue % 360) + 360) % 360) / 60;
|
|
54
|
+
const value = color.brightness / 100;
|
|
55
|
+
const chroma = value * (color.saturation / 100);
|
|
56
|
+
const middle = chroma * (1 - Math.abs((hue % 2) - 1));
|
|
57
|
+
const base = value - chroma;
|
|
58
|
+
const sextants = [
|
|
59
|
+
[chroma, middle, 0],
|
|
60
|
+
[middle, chroma, 0],
|
|
61
|
+
[0, chroma, middle],
|
|
62
|
+
[0, middle, chroma],
|
|
63
|
+
[middle, 0, chroma],
|
|
64
|
+
[chroma, 0, middle],
|
|
65
|
+
];
|
|
66
|
+
const [red, green, blue] = (sextants[Math.floor(hue) % 6] ?? [0, 0, 0]).map((one) => linear(one + base));
|
|
67
|
+
const l = Math.cbrt(0.4122214708 * red + 0.5363325363 * green + 0.0514459929 * blue);
|
|
68
|
+
const m = Math.cbrt(0.2119034982 * red + 0.6806995451 * green + 0.1073969566 * blue);
|
|
69
|
+
const s = Math.cbrt(0.0883024619 * red + 0.2817188376 * green + 0.6299787005 * blue);
|
|
70
|
+
const lightness = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
71
|
+
const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
72
|
+
const b = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
73
|
+
const angle = (Math.atan2(b, a) * 180) / Math.PI;
|
|
74
|
+
return [lightness, Math.hypot(a, b), angle >= 0 ? angle : angle + 360];
|
|
75
|
+
};
|
|
76
|
+
const hueOf = (lightness, chroma, hue, words) => {
|
|
77
|
+
if (chroma < GRAY_BELOW) {
|
|
78
|
+
return [words.gray, lightness];
|
|
79
|
+
}
|
|
80
|
+
for (let at = 0; at < HUES.length; at += 1) {
|
|
81
|
+
const [from, named] = HUES[at];
|
|
82
|
+
const [to, after] = HUES[at + 1] ?? [360, 'pink'];
|
|
83
|
+
if (hue < from || hue >= to) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
let name = named;
|
|
87
|
+
let light = lightness;
|
|
88
|
+
if (name === 'orange') {
|
|
89
|
+
if (lightness < BROWN_BELOW) {
|
|
90
|
+
name = 'brown';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
light = lightness - BROWN_BELOW + DARK_BELOW;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (hue > from + (to - from) / 2 && name !== after) {
|
|
97
|
+
name = `${name} ${after}`;
|
|
98
|
+
}
|
|
99
|
+
else if (name === 'yellow' && lightness < YELLOW_GREEN_BELOW) {
|
|
100
|
+
name = 'yellow green';
|
|
101
|
+
}
|
|
102
|
+
return [words[name] ?? COLOR_WORDS[name] ?? name, light];
|
|
103
|
+
}
|
|
104
|
+
return [words.gray, lightness];
|
|
105
|
+
};
|
|
106
|
+
export const colorName = (color, given = {}, locale = '') => {
|
|
107
|
+
const words = { ...COLOR_WORDS, ...given };
|
|
108
|
+
const [lightness, chroma, angle] = oklch(color);
|
|
109
|
+
if (lightness > 0.999) {
|
|
110
|
+
return words.white;
|
|
111
|
+
}
|
|
112
|
+
if (lightness < 0.001) {
|
|
113
|
+
return words.black;
|
|
114
|
+
}
|
|
115
|
+
const [hue, light] = hueOf(lightness, chroma, angle, words);
|
|
116
|
+
let tone = '';
|
|
117
|
+
if (chroma <= 0.1 && chroma >= GRAY_BELOW) {
|
|
118
|
+
tone = light >= 0.7 ? words.pale : words.grayish;
|
|
119
|
+
}
|
|
120
|
+
else if (chroma >= 0.15) {
|
|
121
|
+
tone = words.vibrant;
|
|
122
|
+
}
|
|
123
|
+
let shade = '';
|
|
124
|
+
if (light < 0.3) {
|
|
125
|
+
shade = words['very dark'];
|
|
126
|
+
}
|
|
127
|
+
else if (light < DARK_BELOW) {
|
|
128
|
+
shade = words.dark;
|
|
129
|
+
}
|
|
130
|
+
else if (light >= 0.85) {
|
|
131
|
+
shade = words['very light'];
|
|
132
|
+
}
|
|
133
|
+
else if (light >= 0.7) {
|
|
134
|
+
shade = words.light;
|
|
135
|
+
}
|
|
136
|
+
const alpha = Math.min(100, Math.max(0, color.alpha)) / 100;
|
|
137
|
+
const sentence = alpha < 1 ? words.transparentColorName : words.colorName;
|
|
138
|
+
const percent = new Intl.NumberFormat(locale || undefined, { style: 'percent' }).format(1 - alpha);
|
|
139
|
+
return sentence
|
|
140
|
+
.replaceAll('{lightness}', () => shade)
|
|
141
|
+
.replaceAll('{chroma}', () => tone)
|
|
142
|
+
.replaceAll('{hue}', () => hue.toLocaleLowerCase(locale || undefined))
|
|
143
|
+
.replaceAll('{percentTransparent}', () => percent)
|
|
144
|
+
.replace(/\s+/g, ' ')
|
|
145
|
+
.trim();
|
|
146
|
+
};
|
|
147
|
+
export const hueName = (color, given = {}, locale = '') => {
|
|
148
|
+
const [lightness, chroma, angle] = oklch(color);
|
|
149
|
+
return hueOf(lightness, chroma, angle, { ...COLOR_WORDS, ...given })[0].toLocaleLowerCase(locale || undefined);
|
|
150
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A count said in one of two sentences, the one for a single thing or the one
|
|
3
|
+
* for any other number, with `{count}` filled in for `locale`.
|
|
4
|
+
*
|
|
5
|
+
* **`Intl.PluralRules` picks the sentence**, because the line between "one"
|
|
6
|
+
* and "other" is the language's: French says "0 résultat" where English says
|
|
7
|
+
* "0 results". A language with more forms than two writes its `one` in the
|
|
8
|
+
* first sentence and the rest in the second.
|
|
9
|
+
*
|
|
10
|
+
* Data Table says how many rows a search left this way, and Listbox how many
|
|
11
|
+
* options a filter left, which Combobox and Command hand down to it.
|
|
12
|
+
*/
|
|
13
|
+
export declare const countLabel: (count: number, one: string, other: string, locale?: string) => string;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { fillTemplate } from '../fill-template/fill-template.js';
|
|
2
|
+
import { formatNumbers } from '../format-numbers/format-numbers.js';
|
|
3
|
+
export const countLabel = (count, one, other, locale = '') => fillTemplate(new Intl.PluralRules(locale || undefined).select(count) === 'one' ? one : other, {
|
|
4
|
+
count: formatNumbers(locale)(count),
|
|
5
|
+
});
|
|
@@ -41,6 +41,18 @@ export interface Color {
|
|
|
41
41
|
}
|
|
42
42
|
/** The syntaxes a value can be written in. */
|
|
43
43
|
export type ColorFormat = 'hex' | 'hsl' | 'rgb';
|
|
44
|
+
/** HSB to bytes, which is the road every written syntax goes down. */
|
|
45
|
+
export declare const toRgb: (color: Color) => {
|
|
46
|
+
blue: number;
|
|
47
|
+
green: number;
|
|
48
|
+
red: number;
|
|
49
|
+
};
|
|
50
|
+
/** HSB to HSL, which is a different pair of numbers for the same color. */
|
|
51
|
+
export declare const toHsl: (color: Color) => {
|
|
52
|
+
hue: number;
|
|
53
|
+
lightness: number;
|
|
54
|
+
saturation: number;
|
|
55
|
+
};
|
|
44
56
|
/**
|
|
45
57
|
* Any of the syntaxes to the working model, or `null` where it is not a color.
|
|
46
58
|
*
|
|
@@ -40,7 +40,7 @@ const rgbToHsb = (red, green, blue, alpha) => {
|
|
|
40
40
|
saturation: Math.round(high === 0 ? 0 : (span / high) * 100),
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
|
-
const toRgb = (color) => {
|
|
43
|
+
export const toRgb = (color) => {
|
|
44
44
|
const hue = (((color.hue % 360) + 360) % 360) / 60;
|
|
45
45
|
const saturation = color.saturation / 100;
|
|
46
46
|
const brightness = color.brightness / 100;
|
|
@@ -62,7 +62,7 @@ const toRgb = (color) => {
|
|
|
62
62
|
red: Math.round((red + base) * 255),
|
|
63
63
|
};
|
|
64
64
|
};
|
|
65
|
-
const toHsl = (color) => {
|
|
65
|
+
export const toHsl = (color) => {
|
|
66
66
|
const saturation = color.saturation / 100;
|
|
67
67
|
const brightness = color.brightness / 100;
|
|
68
68
|
const lightness = (brightness * (2 - saturation)) / 2;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** One part of a date or a time a reader sets on its own. */
|
|
2
|
+
export type SegmentKind = 'day' | 'dayPeriod' | 'hour' | 'minute' | 'month' | 'second' | 'year';
|
|
3
|
+
/** What a segmented field holds: a date, or a time of day. */
|
|
4
|
+
export type SegmentedKind = 'date' | 'time';
|
|
5
|
+
/**
|
|
6
|
+
* The parts set so far, each a number: the month 1 to 12, the hour 0 to 23
|
|
7
|
+
* whatever clock it is shown on, and the half of the day 0 for before noon and
|
|
8
|
+
* 1 for after. A part not yet set is absent.
|
|
9
|
+
*/
|
|
10
|
+
export type Segments = Partial<Record<SegmentKind, number>>;
|
|
11
|
+
/** One place in the field: a segment, or the text a locale writes between two. */
|
|
12
|
+
export type SegmentSlot = {
|
|
13
|
+
kind: SegmentKind;
|
|
14
|
+
} | {
|
|
15
|
+
literal: string;
|
|
16
|
+
};
|
|
17
|
+
/** How the field is laid out and which clock its hour is on. */
|
|
18
|
+
export interface SegmentOptions {
|
|
19
|
+
/** `h12` or `h23`, or absent for the locale's own. */
|
|
20
|
+
hourCycle?: 'h12' | 'h23' | undefined;
|
|
21
|
+
/** Whether a time carries its seconds. */
|
|
22
|
+
seconds?: boolean | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The segments in the order the locale writes them, with what it writes
|
|
26
|
+
* between them, read off `Intl`'s own rendering of a sample.
|
|
27
|
+
*
|
|
28
|
+
* `en-US` is month, day, year with slashes; `de-DE` day, month, year with
|
|
29
|
+
* dots; `ja-JP` year, month, day. A time follows the clock: a twelve hour
|
|
30
|
+
* locale ends with the half of the day.
|
|
31
|
+
*/
|
|
32
|
+
export declare const segmentLayout: (kind: SegmentedKind, locale: string, options?: SegmentOptions) => SegmentSlot[];
|
|
33
|
+
/** Whether a layout writes the hour on a twelve hour clock. */
|
|
34
|
+
export declare const isTwelveHourLayout: (layout: readonly SegmentSlot[]) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* The lowest and highest a segment can be, as it is shown: the hour on a twelve
|
|
37
|
+
* hour clock is 1 to 12, and a day goes as high as its month allows.
|
|
38
|
+
*/
|
|
39
|
+
export declare const segmentRange: (kind: SegmentKind, segments: Segments, twelve: boolean) => {
|
|
40
|
+
max: number;
|
|
41
|
+
min: number;
|
|
42
|
+
};
|
|
43
|
+
/** A segment's number as it is shown: the hour on the clock the field is on. */
|
|
44
|
+
export declare const shownValue: (kind: SegmentKind, segments: Segments, twelve: boolean) => number | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Sets a segment from the number shown for it, which on a twelve hour clock
|
|
47
|
+
* is put back together with the half of the day, and keeps the day inside its
|
|
48
|
+
* month.
|
|
49
|
+
*/
|
|
50
|
+
export declare const setSegment: (segments: Segments, kind: SegmentKind, shown: number | undefined, twelve: boolean) => Segments;
|
|
51
|
+
/**
|
|
52
|
+
* A segment moved by a key's step, wrapping at its ends as a dial does, and
|
|
53
|
+
* starting from where a reader would look for it when it is empty: the lowest
|
|
54
|
+
* going up, the highest going down, and this year for a year.
|
|
55
|
+
*/
|
|
56
|
+
export declare const stepSegment: (segments: Segments, kind: SegmentKind, by: number, twelve: boolean, thisYear?: number) => Segments;
|
|
57
|
+
/**
|
|
58
|
+
* A digit typed into a segment, given what was typed into it already.
|
|
59
|
+
*
|
|
60
|
+
* The digits run on while the number they make still fits and could take
|
|
61
|
+
* another, so `1` then `2` in a month is December; a digit that would overflow
|
|
62
|
+
* starts the number over. `advance` says the segment is full, so focus moves
|
|
63
|
+
* on, which is React Aria's typing: `3` in a month is March and moves to the
|
|
64
|
+
* day, `1` waits for a second digit.
|
|
65
|
+
*/
|
|
66
|
+
export declare const typeDigit: (segments: Segments, kind: SegmentKind, typed: string, digit: string, twelve: boolean) => {
|
|
67
|
+
advance: boolean;
|
|
68
|
+
segments: Segments;
|
|
69
|
+
typed: string;
|
|
70
|
+
};
|
|
71
|
+
/** Where a segment stands when a key arrives: its kind, the field's parts, and what was typed into it. */
|
|
72
|
+
export interface SegmentKeyState {
|
|
73
|
+
/** The segment the key was pressed on. */
|
|
74
|
+
kind: SegmentKind;
|
|
75
|
+
/** The locale the meridiem is written in, whose words a letter picks. */
|
|
76
|
+
locale: string;
|
|
77
|
+
/** The field's parts. */
|
|
78
|
+
segments: Segments;
|
|
79
|
+
/** Whether the field is on a twelve hour clock. */
|
|
80
|
+
twelve: boolean;
|
|
81
|
+
/** The digits typed into the segment so far. */
|
|
82
|
+
typed: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What an editing key does to a segment, or `null` for a key that edits
|
|
86
|
+
* nothing.
|
|
87
|
+
*
|
|
88
|
+
* The arrows step it and wrap, Home and End take it to its lowest and highest,
|
|
89
|
+
* Backspace and Delete empty it, a digit is typed as `typeDigit` types it, and
|
|
90
|
+
* on the meridiem a letter picks the half whose word starts with it. `advance`
|
|
91
|
+
* says the segment is full and focus moves on.
|
|
92
|
+
*/
|
|
93
|
+
export declare const segmentKey: (key: string, { kind, locale, segments, twelve, typed }: SegmentKeyState) => {
|
|
94
|
+
advance: boolean;
|
|
95
|
+
segments: Segments;
|
|
96
|
+
typed: string;
|
|
97
|
+
} | null;
|
|
98
|
+
/** The segments a value names, or none where it names nothing whole. */
|
|
99
|
+
export declare const segmentsOf: (kind: SegmentedKind, value: string) => Segments;
|
|
100
|
+
/**
|
|
101
|
+
* The value the segments add up to, or the empty string while any segment the
|
|
102
|
+
* layout shows is unset.
|
|
103
|
+
*
|
|
104
|
+
* A date that does not exist, the thirtieth of February, cannot be reached,
|
|
105
|
+
* the day being kept inside its month as it is set.
|
|
106
|
+
*/
|
|
107
|
+
export declare const segmentsValue: (kind: SegmentedKind, segments: Segments, layout: readonly SegmentSlot[]) => string;
|
|
108
|
+
/** Whether a reader has set some segments and not all of them. */
|
|
109
|
+
export declare const isPartial: (kind: SegmentedKind, segments: Segments, layout: readonly SegmentSlot[]) => boolean;
|
|
110
|
+
/**
|
|
111
|
+
* What a segment shows: its number, padded the way the locale pads it, the
|
|
112
|
+
* locale's own word for the half of the day, or dashes while it is unset.
|
|
113
|
+
*/
|
|
114
|
+
export declare const segmentText: (kind: SegmentKind, segments: Segments, twelve: boolean, locale: string) => string;
|
|
115
|
+
/**
|
|
116
|
+
* What a screen reader hears for a segment's value: what it shows, and for a
|
|
117
|
+
* month its name as well, "8 – August", which is React Aria's reading. A
|
|
118
|
+
* month heard as a number alone leaves a reader counting.
|
|
119
|
+
*/
|
|
120
|
+
export declare const segmentValueText: (kind: SegmentKind, segments: Segments, twelve: boolean, locale: string) => string;
|
|
121
|
+
/** What a locale calls a segment, such as "month" or "AM/PM", from `Intl`. */
|
|
122
|
+
export declare const segmentName: (kind: SegmentKind, locale: string) => string;
|