@fusion.dev/helix-core 0.143.0 → 0.145.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.
Files changed (107) hide show
  1. package/custom-elements.json +10266 -4161
  2. package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +10 -0
  3. package/dist/controllers/aria-forward-controller/aria-forward-controller.js +6 -0
  4. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +89 -0
  5. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +102 -1
  6. package/dist/controllers/chart-pointer-controller/reading-step.d.ts +57 -0
  7. package/dist/controllers/chart-pointer-controller/reading-step.js +45 -0
  8. package/dist/controllers/copy-controller/copy-controller.d.ts +11 -6
  9. package/dist/controllers/copy-controller/copy-controller.js +11 -9
  10. package/dist/controllers/countdown-controller/countdown-controller.d.ts +55 -0
  11. package/dist/controllers/countdown-controller/countdown-controller.js +57 -0
  12. package/dist/controllers/delay-controller/delay-controller.d.ts +50 -0
  13. package/dist/controllers/delay-controller/delay-controller.js +54 -0
  14. package/dist/controllers/focus-scope-controller/focus-scope-controller.d.ts +56 -0
  15. package/dist/controllers/focus-scope-controller/focus-scope-controller.js +71 -0
  16. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +11 -2
  17. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +4 -1
  18. package/dist/controllers/open-phase-controller/open-phase-controller.d.ts +49 -0
  19. package/dist/controllers/open-phase-controller/open-phase-controller.js +41 -0
  20. package/dist/controllers/source-controller/source-controller.d.ts +16 -0
  21. package/dist/controllers/source-controller/source-controller.js +44 -1
  22. package/dist/controllers/status-controller/status-controller.d.ts +47 -0
  23. package/dist/controllers/status-controller/status-controller.js +34 -0
  24. package/dist/controllers/swipe-controller/swipe-controller.d.ts +61 -0
  25. package/dist/controllers/swipe-controller/swipe-controller.js +106 -0
  26. package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +68 -35
  27. package/dist/controllers/virtualize-controller/virtualize-controller.js +77 -10
  28. package/dist/events/events.d.ts +221 -0
  29. package/dist/events/events.js +150 -0
  30. package/dist/helix-element/helix-element.js +14 -1
  31. package/dist/helix-element/react-bridge.d.ts +9 -4
  32. package/dist/helix-element/react-bridge.js +14 -1
  33. package/dist/index.d.ts +31 -9
  34. package/dist/index.js +29 -7
  35. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +7 -2
  36. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +5 -1
  37. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +10 -0
  38. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +9 -0
  39. package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +2 -0
  40. package/dist/mixins/mixin-dismissible/mixin-dismissible.js +7 -3
  41. package/dist/mixins/mixin-field/mixin-field.d.ts +2 -0
  42. package/dist/mixins/mixin-field/mixin-field.js +8 -0
  43. package/dist/mixins/mixin-picker-editing/mixin-picker-editing.d.ts +16 -6
  44. package/dist/mixins/mixin-picker-editing/mixin-picker-editing.js +19 -1
  45. package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +5 -4
  46. package/dist/mixins/mixin-picker-field/mixin-picker-field.js +12 -1
  47. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +67 -0
  48. package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +243 -0
  49. package/dist/utils/arrow/arrow.d.ts +15 -0
  50. package/dist/utils/arrow/arrow.js +2 -0
  51. package/dist/utils/audit-name/audit-name.d.ts +28 -0
  52. package/dist/utils/audit-name/audit-name.js +95 -0
  53. package/dist/utils/calendar/calendar.d.ts +117 -0
  54. package/dist/utils/calendar/calendar.js +96 -0
  55. package/dist/utils/chord/chord.d.ts +43 -0
  56. package/dist/utils/chord/chord.js +53 -0
  57. package/dist/utils/clock/clock.d.ts +87 -0
  58. package/dist/utils/clock/clock.js +68 -0
  59. package/dist/utils/color-name/color-name.d.ts +28 -0
  60. package/dist/utils/color-name/color-name.js +150 -0
  61. package/dist/utils/count-label/count-label.d.ts +13 -0
  62. package/dist/utils/count-label/count-label.js +5 -0
  63. package/dist/utils/css-color/css-color.d.ts +12 -0
  64. package/dist/utils/css-color/css-color.js +2 -2
  65. package/dist/utils/date-segments/date-segments.d.ts +122 -0
  66. package/dist/utils/date-segments/date-segments.js +206 -0
  67. package/dist/utils/draw-dismiss/draw-dismiss.d.ts +30 -0
  68. package/dist/utils/draw-dismiss/draw-dismiss.js +16 -0
  69. package/dist/utils/draw-icon/draw-icon.d.ts +7 -6
  70. package/dist/utils/draw-tooltip/draw-tooltip.d.ts +15 -21
  71. package/dist/utils/draw-tooltip/draw-tooltip.js +11 -5
  72. package/dist/utils/draw-tooltip/spoken-reading.d.ts +10 -0
  73. package/dist/utils/draw-tooltip/spoken-reading.js +10 -0
  74. package/dist/utils/follow-link/follow-link.d.ts +46 -0
  75. package/dist/utils/follow-link/follow-link.js +37 -0
  76. package/dist/utils/heading-in/heading-in.d.ts +19 -0
  77. package/dist/utils/heading-in/heading-in.js +14 -0
  78. package/dist/utils/number-format/number-format.d.ts +23 -0
  79. package/dist/utils/number-format/number-format.js +55 -0
  80. package/dist/utils/platform/platform.d.ts +23 -0
  81. package/dist/utils/platform/platform.js +11 -0
  82. package/dist/utils/range-add/range-add.d.ts +19 -0
  83. package/dist/utils/range-add/range-add.js +5 -0
  84. package/dist/utils/read-color/read-color.d.ts +14 -0
  85. package/dist/utils/read-color/read-color.js +13 -0
  86. package/dist/utils/read-series/read-series.d.ts +6 -0
  87. package/dist/utils/read-series/read-series.js +1 -0
  88. package/dist/utils/reflect-elements/reflect-elements.d.ts +37 -0
  89. package/dist/utils/reflect-elements/reflect-elements.js +40 -0
  90. package/dist/utils/relative-time/relative-time.d.ts +13 -1
  91. package/dist/utils/relative-time/relative-time.js +7 -4
  92. package/dist/utils/report-runtime/report-runtime.d.ts +5 -1
  93. package/dist/utils/roving-stop/roving-stop.d.ts +20 -0
  94. package/dist/utils/roving-stop/roving-stop.js +3 -0
  95. package/dist/utils/swipe/swipe.d.ts +63 -0
  96. package/dist/utils/swipe/swipe.js +48 -0
  97. package/dist/utils/tab-stops/tab-stops.d.ts +16 -0
  98. package/dist/utils/tab-stops/tab-stops.js +22 -0
  99. package/dist/utils/value-as/value-as.d.ts +20 -0
  100. package/dist/utils/value-as/value-as.js +16 -0
  101. package/dist/utils/write-time/write-time.d.ts +15 -5
  102. package/dist/utils/write-time/write-time.js +7 -7
  103. package/dist/utils/written-list/written-list.d.ts +18 -0
  104. package/dist/utils/written-list/written-list.js +8 -0
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/package.json +4 -4
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Today, in the reader's own timezone.
3
+ *
4
+ * **`toISOString` converts to UTC first, which makes "today" a different day
5
+ * for most of the planet.** Anywhere west of UTC, from late afternoon onward,
6
+ * `fromDate(new Date())` names tomorrow. The parts are read locally instead and
7
+ * pasted together, which is the only way to get a calendar date out of a `Date`
8
+ * without a conversion happening on the way.
9
+ */
10
+ export declare const localToday: (now?: Date) => string;
11
+ /**
12
+ * The first day of the week where a language is spoken, `0` for Sunday.
13
+ *
14
+ * From `Intl.Locale`'s week information, which an engine offers as
15
+ * `getWeekInfo()` or, in older versions, as `weekInfo`. Sunday where it offers
16
+ * neither, as Firefox does not, or where the tag cannot be read.
17
+ */
18
+ export declare const weekStartOf: (locale: string) => number;
19
+ /** A month as `YYYY-MM`. */
20
+ export declare const monthOf: (date: Date) => string;
21
+ /** A date some days away, which may be negative, at midnight UTC. */
22
+ export declare const addDays: (date: Date, days: number) => Date;
23
+ /** Whether two dates fall in the same month of the same year. */
24
+ export declare const sameMonth: (left: Date, right: Date) => boolean;
25
+ /**
26
+ * A date some months away, clamped, so the last of a long month lands on the
27
+ * last of a short one instead of rolling into the month after it.
28
+ */
29
+ export declare const addMonths: (date: Date, months: number) => Date;
30
+ /** What bars a date, besides `min` and `max`. */
31
+ export interface Barring {
32
+ /** Dates as `YYYY-MM-DD`, separated by commas or spaces. */
33
+ disableDates: string;
34
+ /** Days of the week, `0` for Sunday, separated by commas or spaces. */
35
+ disableDays: string;
36
+ /** A consumer's own rule, asked of each date. */
37
+ isDateUnavailable?: ((date: string) => boolean) | undefined;
38
+ /** Where `step` counts from, or empty for 1 January 1970. */
39
+ min: string;
40
+ /** How far apart the selectable dates are, in days. */
41
+ step: number;
42
+ }
43
+ /**
44
+ * Why a date cannot be chosen, besides `min` and `max`, or `null` where it can.
45
+ *
46
+ * `listed` is a date the rules name: in `disable-dates`, on a day in
47
+ * `disable-days`, or one `isDateUnavailable` refuses. `step` is a date `step`
48
+ * skips, counted from `min` where there is one and from the epoch where there is
49
+ * not, which is the platform's own base. Whole days, because both ends are
50
+ * midnight UTC and neither carries a time.
51
+ */
52
+ export declare const barredBy: (date: Date, rules: Barring) => 'listed' | 'step' | null;
53
+ /** The views a calendar can show: the days of a month, the months of a year, or a page of years. */
54
+ export type CalendarView = 'day' | 'month' | 'year';
55
+ /** How many years the year view shows at once, in rows of three. */
56
+ export declare const YEARS_SHOWN = 12;
57
+ /** The first year on the page of years that holds `year`. */
58
+ export declare const yearPageOf: (year: number) => number;
59
+ /**
60
+ * Where a key moves within the months of a year or a page of years, laid out
61
+ * in rows of three, as an offset from `at`, the index on the page.
62
+ *
63
+ * `along` is which way the inline keys go, `1` or `-1`, or `0` for a key that
64
+ * is not one. The vertical keys move a row, Home and End to the ends of a row,
65
+ * and PageUp and PageDown a whole page. `null` is a key the grid leaves alone.
66
+ */
67
+ export declare const periodMove: (key: string, at: number, along: number) => number | null;
68
+ /** Whether a month, as its first day, lies wholly outside `min` and `max`, either of which may be empty. */
69
+ export declare const monthOut: (first: Date, min: string, max: string) => boolean;
70
+ /** Whether a year lies wholly outside `min` and `max`, either of which may be empty. */
71
+ export declare const yearOut: (year: number, min: string, max: string) => boolean;
72
+ /** One month or year in the months or the years view. */
73
+ export interface PeriodCell {
74
+ /** The month's index in its year, or the year. */
75
+ at: number;
76
+ /** Whether it is the month or year the calendar shows. */
77
+ chosen: boolean;
78
+ /** Whether today falls in it. */
79
+ current: boolean;
80
+ /** Its first day. */
81
+ first: Date;
82
+ /** Whether it lies wholly outside `min` and `max`. */
83
+ out: boolean;
84
+ }
85
+ /** What the months or the years view is drawn from. */
86
+ export interface PeriodPage {
87
+ /** The latest date, or empty. */
88
+ max: string;
89
+ /** The earliest date, or empty. */
90
+ min: string;
91
+ /** The year the months view is on, or any year on the page of years. */
92
+ page: number;
93
+ /** The first of the month the calendar shows. */
94
+ shown: Date;
95
+ /** Today, as `YYYY-MM-DD`. */
96
+ today: string;
97
+ /** Which view: `month` for the months of a year, `year` for a page of years. */
98
+ view: 'month' | 'year';
99
+ }
100
+ /**
101
+ * The twelve cells of the months or the years view, in reading order: the
102
+ * months of `page`, or the page of years that holds it.
103
+ */
104
+ export declare const periodCells: ({ max, min, page, shown, today, view }: PeriodPage) => PeriodCell[];
105
+ /**
106
+ * Where a key takes the tab stop in the months or the years, or `null` for a
107
+ * key the grid leaves alone.
108
+ *
109
+ * `stop` is the month's index or the year the stop is on, `year` the year the
110
+ * months view shows, and `along` which way the inline keys go, as
111
+ * `periodMove` takes it. A move off the months of one year lands in the next
112
+ * or the last, and a move off a page of years turns the page.
113
+ */
114
+ export declare const periodKey: (view: 'month' | 'year', stop: number, year: number, key: string, along: number) => {
115
+ focus: number;
116
+ pageYear: number;
117
+ } | null;
@@ -0,0 +1,96 @@
1
+ import { valueAsDate as asDate, dateAsValue as fromDate } from '../value-as/value-as.js';
2
+ import { writtenList } from '../written-list/written-list.js';
3
+ export const localToday = (now = new Date()) => {
4
+ const month = `${now.getMonth() + 1}`.padStart(2, '0');
5
+ const day = `${now.getDate()}`.padStart(2, '0');
6
+ return `${now.getFullYear()}-${month}-${day}`;
7
+ };
8
+ export const weekStartOf = (locale) => {
9
+ try {
10
+ const tag = new Intl.Locale(locale);
11
+ const first = tag.getWeekInfo?.().firstDay ?? tag.weekInfo?.firstDay;
12
+ return first === undefined ? 0 : first % 7;
13
+ }
14
+ catch {
15
+ return 0;
16
+ }
17
+ };
18
+ export const monthOf = (date) => `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
19
+ export const addDays = (date, days) => new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days));
20
+ export const sameMonth = (left, right) => left.getUTCFullYear() === right.getUTCFullYear() && left.getUTCMonth() === right.getUTCMonth();
21
+ export const addMonths = (date, months) => {
22
+ const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));
23
+ const last = new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0)).getUTCDate();
24
+ return new Date(Date.UTC(target.getUTCFullYear(), target.getUTCMonth(), Math.min(date.getUTCDate(), last)));
25
+ };
26
+ export const barredBy = (date, rules) => {
27
+ const iso = fromDate(date);
28
+ if (writtenList(rules.disableDates).includes(iso) ||
29
+ writtenList(rules.disableDays).includes(String(date.getUTCDay())) ||
30
+ rules.isDateUnavailable?.(iso) === true) {
31
+ return 'listed';
32
+ }
33
+ const step = Math.max(1, Math.floor(rules.step) || 1);
34
+ if (step > 1) {
35
+ const base = rules.min === '' ? 0 : (asDate(rules.min)?.getTime() ?? 0);
36
+ if (Math.round((date.getTime() - base) / 86_400_000) % step !== 0) {
37
+ return 'step';
38
+ }
39
+ }
40
+ return null;
41
+ };
42
+ export const YEARS_SHOWN = 12;
43
+ export const yearPageOf = (year) => Math.floor(year / YEARS_SHOWN) * YEARS_SHOWN;
44
+ export const periodMove = (key, at, along) => {
45
+ if (along !== 0) {
46
+ return along;
47
+ }
48
+ switch (key) {
49
+ case 'ArrowUp':
50
+ return -3;
51
+ case 'ArrowDown':
52
+ return 3;
53
+ case 'Home':
54
+ return -(at % 3);
55
+ case 'End':
56
+ return 2 - (at % 3);
57
+ case 'PageUp':
58
+ return -12;
59
+ case 'PageDown':
60
+ return 12;
61
+ default:
62
+ return null;
63
+ }
64
+ };
65
+ export const monthOut = (first, min, max) => {
66
+ const month = monthOf(first);
67
+ return (min !== '' && month < min.slice(0, 7)) || (max !== '' && month > max.slice(0, 7));
68
+ };
69
+ export const yearOut = (year, min, max) => (min !== '' && year < Number(min.slice(0, 4))) || (max !== '' && year > Number(max.slice(0, 4)));
70
+ export const periodCells = ({ max, min, page, shown, today, view }) => {
71
+ const years = view === 'year';
72
+ const start = years ? yearPageOf(page) : page;
73
+ return Array.from({ length: 12 }, (_, index) => {
74
+ const at = years ? start + index : index;
75
+ const first = new Date(Date.UTC(years ? at : start, years ? 0 : index, 1));
76
+ return {
77
+ at,
78
+ chosen: years ? at === shown.getUTCFullYear() : monthOf(first) === monthOf(shown),
79
+ current: years ? String(at) === today.slice(0, 4) : monthOf(first) === today.slice(0, 7),
80
+ first,
81
+ out: years ? yearOut(at, min, max) : monthOut(first, min, max),
82
+ };
83
+ });
84
+ };
85
+ export const periodKey = (view, stop, year, key, along) => {
86
+ const index = view === 'year' ? stop - yearPageOf(stop) : stop;
87
+ const delta = periodMove(key, index, along);
88
+ if (delta === null) {
89
+ return null;
90
+ }
91
+ if (view === 'year') {
92
+ return { focus: stop + delta, pageYear: stop + delta };
93
+ }
94
+ const months = year * 12 + stop + delta;
95
+ return { focus: ((months % 12) + 12) % 12, pageYear: Math.floor(months / 12) };
96
+ };
@@ -0,0 +1,43 @@
1
+ /** The keys of a chord, in the order written, with spaces and empty entries dropped. */
2
+ export declare const chordParts: (keys: string) => string[];
3
+ /**
4
+ * A chord as `aria-keyshortcuts` writes it: `mod+k` is `Meta+K` on an Apple
5
+ * keyboard and `Control+K` elsewhere. A letter is written in capitals and a
6
+ * symbol as itself. Empty for no keys.
7
+ */
8
+ export declare const ariaChord: (keys: string, apple: boolean) => string;
9
+ /**
10
+ * Whether a chord types a character, and so is indistinguishable from typing.
11
+ *
12
+ * One code point means a character rather than a name: `/` and `?` are
13
+ * printable, `escape` and `enter` are not. **Shift is allowed beside it**,
14
+ * because Shift is how a character is typed: `shift+?` and `shift+a` are a
15
+ * question mark and a capital A in a field.
16
+ */
17
+ export declare const printableChord: (keys: string) => boolean;
18
+ /** What a key press carries that a chord is compared against. */
19
+ export interface ChordPress {
20
+ altKey: boolean;
21
+ ctrlKey: boolean;
22
+ key: string;
23
+ metaKey: boolean;
24
+ shiftKey: boolean;
25
+ }
26
+ /**
27
+ * Whether a press is the chord, modifiers and all.
28
+ *
29
+ * `mod` is one key, the platform's: Command where `apple` and Control
30
+ * elsewhere. The other one is then checked like any modifier the chord does
31
+ * not name, so Control and Command together do not answer `mod+k`. `cmd` and
32
+ * `meta` are Command, `alt` and `opt` are Option.
33
+ *
34
+ * **A symbol is typed with Shift, so Shift says nothing about it.** On a US
35
+ * layout `?` arrives as `?` with `shiftKey` set, and a chord of `?` that
36
+ * refused Shift could never be pressed at all. A letter has a shifted form of
37
+ * its own, so `a` still refuses Shift and `shift+a` still asks for it.
38
+ *
39
+ * Every other modifier is checked in both directions, so `mod+k` is not
40
+ * answered by `shift+mod+k`: a chord a reader did not press is a chord that
41
+ * should go to whatever did want it.
42
+ */
43
+ export declare const matchesChord: (press: ChordPress, keys: string, apple: boolean) => boolean;
@@ -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
+ });