@fusion.dev/helix-core 0.154.0 → 0.156.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.
@@ -14633,6 +14633,30 @@
14633
14633
  "module": "./utils/calendar/calendar.js"
14634
14634
  }
14635
14635
  },
14636
+ {
14637
+ "kind": "js",
14638
+ "name": "MONTH_CALENDARS",
14639
+ "declaration": {
14640
+ "name": "MONTH_CALENDARS",
14641
+ "module": "./utils/calendar/calendar.js"
14642
+ }
14643
+ },
14644
+ {
14645
+ "kind": "js",
14646
+ "name": "MonthCalendar",
14647
+ "declaration": {
14648
+ "name": "MonthCalendar",
14649
+ "module": "./utils/calendar/calendar.js"
14650
+ }
14651
+ },
14652
+ {
14653
+ "kind": "js",
14654
+ "name": "MonthSystem",
14655
+ "declaration": {
14656
+ "name": "MonthSystem",
14657
+ "module": "./utils/calendar/calendar.js"
14658
+ }
14659
+ },
14636
14660
  {
14637
14661
  "kind": "js",
14638
14662
  "name": "monthOf",
@@ -14649,6 +14673,14 @@
14649
14673
  "module": "./utils/calendar/calendar.js"
14650
14674
  }
14651
14675
  },
14676
+ {
14677
+ "kind": "js",
14678
+ "name": "monthSystem",
14679
+ "declaration": {
14680
+ "name": "monthSystem",
14681
+ "module": "./utils/calendar/calendar.js"
14682
+ }
14683
+ },
14652
14684
  {
14653
14685
  "kind": "js",
14654
14686
  "name": "PeriodCell",
@@ -14697,6 +14729,22 @@
14697
14729
  "module": "./utils/calendar/calendar.js"
14698
14730
  }
14699
14731
  },
14732
+ {
14733
+ "kind": "js",
14734
+ "name": "todayIn",
14735
+ "declaration": {
14736
+ "name": "todayIn",
14737
+ "module": "./utils/calendar/calendar.js"
14738
+ }
14739
+ },
14740
+ {
14741
+ "kind": "js",
14742
+ "name": "weekendOf",
14743
+ "declaration": {
14744
+ "name": "weekendOf",
14745
+ "module": "./utils/calendar/calendar.js"
14746
+ }
14747
+ },
14700
14748
  {
14701
14749
  "kind": "js",
14702
14750
  "name": "weekStartOf",
@@ -15017,6 +15065,14 @@
15017
15065
  "module": "./utils/css-color/css-color.js"
15018
15066
  }
15019
15067
  },
15068
+ {
15069
+ "kind": "js",
15070
+ "name": "toOklch",
15071
+ "declaration": {
15072
+ "name": "toOklch",
15073
+ "module": "./utils/css-color/css-color.js"
15074
+ }
15075
+ },
15020
15076
  {
15021
15077
  "kind": "js",
15022
15078
  "name": "toRgb",
@@ -19770,6 +19826,17 @@
19770
19826
  "default": "'label'",
19771
19827
  "description": "The id the label carries, for an element in the template to be named by.\n\n**What a template draws is not the host**, which is what `hostAria`\nnames: a rating's radiogroup, a pin input's row of cells, a listbox's\nlist and a range slider's two thumbs are elements of their own, and they\nhad no name but a `label` string, or none at all. In the same tree as\nthe label, so a plain `aria-labelledby` resolves, and it names them by\nwhatever the label holds, a slotted one included. One id for both shapes,\nsince a family draws one or the other."
19772
19828
  },
19829
+ {
19830
+ "kind": "field",
19831
+ "name": "namedBy",
19832
+ "type": {
19833
+ "text": "string | null"
19834
+ },
19835
+ "privacy": "protected",
19836
+ "default": "null",
19837
+ "description": "The name a screen reader says for the field where it shows no label, as\nthe platform's `aria-label`. It is drawn as a label and clipped, so it\nnames the control inside, and a new one renders at once.\n\n**An `aria-label` is read when the field renders, and nothing else would\nrender it again.** It is the platform's attribute and no property of this\nelement's, so writing a new one scheduled no update and the label kept\nthe old words. Data Table meets this on every column move: each row's\ncheckbox is named by the first column, so reordering renames every one.\n\nA reactive property on the attribute, which is the one way Lit observes\none. Overriding `observedAttributes` in a mixin compiled `this` to the\nmixin's own class, so every field observed only this mixin's attributes\nand dropped its own. Not reflected: the attribute is the consumer's.",
19838
+ "attribute": "aria-label"
19839
+ },
19773
19840
  {
19774
19841
  "kind": "field",
19775
19842
  "name": "labeled",
@@ -20026,7 +20093,9 @@
20026
20093
  "type": {
20027
20094
  "text": "string | null"
20028
20095
  },
20029
- "default": "null"
20096
+ "default": "null",
20097
+ "description": "The name a screen reader says for the field where it shows no label, as\nthe platform's `aria-label`. It is drawn as a label and clipped, so it\nnames the control inside, and a new one renders at once.\n\n**An `aria-label` is read when the field renders, and nothing else would\nrender it again.** It is the platform's attribute and no property of this\nelement's, so writing a new one scheduled no update and the label kept\nthe old words. Data Table meets this on every column move: each row's\ncheckbox is named by the first column, so reordering renames every one.\n\nA reactive property on the attribute, which is the one way Lit observes\none. Overriding `observedAttributes` in a mixin compiled `this` to the\nmixin's own class, so every field observed only this mixin's attributes\nand dropped its own. Not reflected: the attribute is the consumer's.",
20098
+ "fieldName": "namedBy"
20030
20099
  },
20031
20100
  {
20032
20101
  "name": "feedback",
@@ -23727,6 +23796,44 @@
23727
23796
  ],
23728
23797
  "description": "Today, in the reader's own timezone.\n\n**`toISOString` converts to UTC first, which makes \"today\" a different day\nfor most of the planet.** Anywhere west of UTC, from late afternoon onward,\n`fromDate(new Date())` names tomorrow. The parts are read locally instead and\npasted together, which is the only way to get a calendar date out of a `Date`\nwithout a conversion happening on the way."
23729
23798
  },
23799
+ {
23800
+ "kind": "function",
23801
+ "name": "todayIn",
23802
+ "return": {
23803
+ "type": {
23804
+ "text": "string"
23805
+ }
23806
+ },
23807
+ "parameters": [
23808
+ {
23809
+ "name": "timeZone",
23810
+ "default": "''"
23811
+ },
23812
+ {
23813
+ "name": "now",
23814
+ "default": "new Date()"
23815
+ }
23816
+ ],
23817
+ "description": "Today in a time zone, such as `America/New_York`, as `YYYY-MM-DD`: the\nreader's own where `timeZone` is empty, as `localToday` reads it. A zone\n`Intl` does not know is read as the reader's own too."
23818
+ },
23819
+ {
23820
+ "kind": "function",
23821
+ "name": "weekendOf",
23822
+ "return": {
23823
+ "type": {
23824
+ "text": "number[]"
23825
+ }
23826
+ },
23827
+ "parameters": [
23828
+ {
23829
+ "name": "locale",
23830
+ "type": {
23831
+ "text": "string"
23832
+ }
23833
+ }
23834
+ ],
23835
+ "description": "The days of the weekend where a language is spoken, `0` for Sunday.\n\nFrom `Intl.Locale`'s week information, as `weekStartOf` reads it: Friday and\nSaturday in `ar-EG`, Sunday alone in `hi-IN`. Saturday and Sunday where the\nengine offers none."
23836
+ },
23730
23837
  {
23731
23838
  "kind": "function",
23732
23839
  "name": "weekStartOf",
@@ -23857,6 +23964,31 @@
23857
23964
  ],
23858
23965
  "description": "A date some months away, clamped, so the last of a long month lands on the\nlast of a short one instead of rolling into the month after it."
23859
23966
  },
23967
+ {
23968
+ "kind": "variable",
23969
+ "name": "MONTH_CALENDARS",
23970
+ "type": {
23971
+ "text": "readonly string[]"
23972
+ },
23973
+ "default": "[ 'coptic', 'ethiopic', 'hebrew', 'indian', 'islamic-civil', 'islamic-umalqura', 'persian', ]",
23974
+ "description": "The `MonthCalendar` names, for a check at run time."
23975
+ },
23976
+ {
23977
+ "kind": "function",
23978
+ "name": "monthSystem",
23979
+ "return": {
23980
+ "type": {
23981
+ "text": "MonthSystem"
23982
+ }
23983
+ },
23984
+ "parameters": [
23985
+ {
23986
+ "name": "calendar",
23987
+ "default": "''"
23988
+ }
23989
+ ],
23990
+ "description": "The months a calendar pages through: its own for a `MonthCalendar` name,\nand the Gregorian months for every other."
23991
+ },
23860
23992
  {
23861
23993
  "kind": "function",
23862
23994
  "name": "barredBy",
@@ -24068,6 +24200,22 @@
24068
24200
  "module": "src/utils/calendar/calendar.ts"
24069
24201
  }
24070
24202
  },
24203
+ {
24204
+ "kind": "js",
24205
+ "name": "todayIn",
24206
+ "declaration": {
24207
+ "name": "todayIn",
24208
+ "module": "src/utils/calendar/calendar.ts"
24209
+ }
24210
+ },
24211
+ {
24212
+ "kind": "js",
24213
+ "name": "weekendOf",
24214
+ "declaration": {
24215
+ "name": "weekendOf",
24216
+ "module": "src/utils/calendar/calendar.ts"
24217
+ }
24218
+ },
24071
24219
  {
24072
24220
  "kind": "js",
24073
24221
  "name": "weekStartOf",
@@ -24116,6 +24264,22 @@
24116
24264
  "module": "src/utils/calendar/calendar.ts"
24117
24265
  }
24118
24266
  },
24267
+ {
24268
+ "kind": "js",
24269
+ "name": "MONTH_CALENDARS",
24270
+ "declaration": {
24271
+ "name": "MONTH_CALENDARS",
24272
+ "module": "src/utils/calendar/calendar.ts"
24273
+ }
24274
+ },
24275
+ {
24276
+ "kind": "js",
24277
+ "name": "monthSystem",
24278
+ "declaration": {
24279
+ "name": "monthSystem",
24280
+ "module": "src/utils/calendar/calendar.ts"
24281
+ }
24282
+ },
24119
24283
  {
24120
24284
  "kind": "js",
24121
24285
  "name": "barredBy",
@@ -25170,6 +25334,24 @@
25170
25334
  ],
25171
25335
  "description": "HSB to HSL, which is a different pair of numbers for the same color."
25172
25336
  },
25337
+ {
25338
+ "kind": "function",
25339
+ "name": "toOklch",
25340
+ "return": {
25341
+ "type": {
25342
+ "text": "{ chroma: number; hue: number; lightness: number }"
25343
+ }
25344
+ },
25345
+ "parameters": [
25346
+ {
25347
+ "name": "color",
25348
+ "type": {
25349
+ "text": "Color"
25350
+ }
25351
+ }
25352
+ ],
25353
+ "description": "Bytes to OKLCH: lightness 0 to 1, chroma from 0, hue in degrees. Björn\nOttosson's OKLab matrices, as CSS Color 4 defines `oklch()`."
25354
+ },
25173
25355
  {
25174
25356
  "kind": "function",
25175
25357
  "name": "parseColor",
@@ -25254,6 +25436,14 @@
25254
25436
  "module": "src/utils/css-color/css-color.ts"
25255
25437
  }
25256
25438
  },
25439
+ {
25440
+ "kind": "js",
25441
+ "name": "toOklch",
25442
+ "declaration": {
25443
+ "name": "toOklch",
25444
+ "module": "src/utils/css-color/css-color.ts"
25445
+ }
25446
+ },
25257
25447
  {
25258
25448
  "kind": "js",
25259
25449
  "name": "parseColor",
@@ -32230,7 +32420,7 @@
32230
32420
  "type": {
32231
32421
  "text": "string"
32232
32422
  },
32233
- "default": "'0.154.0'",
32423
+ "default": "'0.156.0'",
32234
32424
  "description": "The version of the package this file ships in.\n\n**Written by `fusion/scripts/version.mjs`** wherever it writes the package's\n`package.json`, and stamped back into the repository beside it after a\nrelease, so the two never disagree; a spec beside this holds them to it.\nNothing else in the running code knows which release it is."
32235
32425
  }
32236
32426
  ],
@@ -32790,6 +32980,6 @@
32790
32980
  ],
32791
32981
  "helix": {
32792
32982
  "package": "@fusion.dev/helix-core",
32793
- "version": "0.154.0"
32983
+ "version": "0.156.0"
32794
32984
  }
32795
32985
  }
@@ -281,6 +281,13 @@ export interface ReorderDetail extends ChangeDetail {
281
281
  keys: string[];
282
282
  /** What moved: a column of a data table, an item of a list, or a row of a data table. */
283
283
  kind: 'column' | 'item' | 'row';
284
+ /**
285
+ * The key of the branch a tree item moves into, and `''` for the top level,
286
+ * where a `reorderable` tree moves an item into another branch. `keys` are
287
+ * then that branch's items in their new order. Absent for a move among
288
+ * siblings.
289
+ */
290
+ parent?: string;
284
291
  /** Where the key is after the move, counting from 0 in `keys`. */
285
292
  to: number;
286
293
  /**
package/dist/index.d.ts CHANGED
@@ -56,7 +56,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
56
56
  export { arrowReach, sideOf } from './utils/arrow/arrow.js';
57
57
  export { assignSlots } from './utils/assign-slots/assign-slots.js';
58
58
  export { type DrawAutofillOptions, drawAutofill, } from './utils/autofill-select/autofill-select.js';
59
- export { addDays, addMonths, type Barring, barredBy, type CalendarView, calendarLocale, type DateCalendar, localToday, monthOf, monthOut, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
59
+ export { addDays, addMonths, type Barring, barredBy, type CalendarView, calendarLocale, type DateCalendar, localToday, MONTH_CALENDARS, type MonthCalendar, type MonthSystem, monthOf, monthOut, monthSystem, type PeriodCell, type PeriodPage, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
60
60
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
61
61
  export { centerInView } from './utils/center-in-view/center-in-view.js';
62
62
  export { ariaChord, type ChordPress, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
@@ -68,7 +68,7 @@ export { type ColorWords, colorName, hueName } from './utils/color-name/color-na
68
68
  export { columnStep } from './utils/column-step/column-step.js';
69
69
  export { countLabel } from './utils/count-label/count-label.js';
70
70
  export { createSheet } from './utils/create-sheet/create-sheet.js';
71
- export { type Color, type ColorFormat, formatColor, parseColor, toCss, toHsl, toRgb, } from './utils/css-color/css-color.js';
71
+ export { type Color, type ColorFormat, formatColor, parseColor, toCss, toHsl, toOklch, toRgb, } from './utils/css-color/css-color.js';
72
72
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
73
73
  export { type DataOption, type DataOptionItem, dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
74
74
  export { segmentName } from './utils/date-segments/date-segments.js';
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ export { anchorElement } from './utils/anchor-element/anchor-element.js';
56
56
  export { arrowReach, sideOf } from './utils/arrow/arrow.js';
57
57
  export { assignSlots } from './utils/assign-slots/assign-slots.js';
58
58
  export { drawAutofill, } from './utils/autofill-select/autofill-select.js';
59
- export { addDays, addMonths, barredBy, calendarLocale, localToday, monthOf, monthOut, periodCells, periodKey, periodMove, sameMonth, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
59
+ export { addDays, addMonths, barredBy, calendarLocale, localToday, MONTH_CALENDARS, monthOf, monthOut, monthSystem, periodCells, periodKey, periodMove, sameMonth, todayIn, weekendOf, weekStartOf, YEARS_SHOWN, yearOut, yearPageOf, } from './utils/calendar/calendar.js';
60
60
  export { capturePointer } from './utils/capture-pointer/capture-pointer.js';
61
61
  export { centerInView } from './utils/center-in-view/center-in-view.js';
62
62
  export { ariaChord, chordParts, matchesChord, printableChord, selectsAll, } from './utils/chord/chord.js';
@@ -68,7 +68,7 @@ export { colorName, hueName } from './utils/color-name/color-name.js';
68
68
  export { columnStep } from './utils/column-step/column-step.js';
69
69
  export { countLabel } from './utils/count-label/count-label.js';
70
70
  export { createSheet } from './utils/create-sheet/create-sheet.js';
71
- export { formatColor, parseColor, toCss, toHsl, toRgb, } from './utils/css-color/css-color.js';
71
+ export { formatColor, parseColor, toCss, toHsl, toOklch, toRgb, } from './utils/css-color/css-color.js';
72
72
  export { customElement, definedTags, duplicateRegistrations, } from './utils/custom-element/custom-element.js';
73
73
  export { dataOptions, drawOptions, windowOf, } from './utils/data-options/data-options.js';
74
74
  export { segmentName } from './utils/date-segments/date-segments.js';
@@ -8,6 +8,20 @@
8
8
  * without a conversion happening on the way.
9
9
  */
10
10
  export declare const localToday: (now?: Date) => string;
11
+ /**
12
+ * Today in a time zone, such as `America/New_York`, as `YYYY-MM-DD`: the
13
+ * reader's own where `timeZone` is empty, as `localToday` reads it. A zone
14
+ * `Intl` does not know is read as the reader's own too.
15
+ */
16
+ export declare const todayIn: (timeZone?: string, now?: Date) => string;
17
+ /**
18
+ * The days of the weekend where a language is spoken, `0` for Sunday.
19
+ *
20
+ * From `Intl.Locale`'s week information, as `weekStartOf` reads it: Friday and
21
+ * Saturday in `ar-EG`, Sunday alone in `hi-IN`. Saturday and Sunday where the
22
+ * engine offers none.
23
+ */
24
+ export declare const weekendOf: (locale: string) => number[];
11
25
  /**
12
26
  * The first day of the week where a language is spoken, `0` for Sunday.
13
27
  *
@@ -54,6 +68,37 @@ export declare const sameMonth: (left: Date, right: Date) => boolean;
54
68
  * last of a short one instead of rolling into the month after it.
55
69
  */
56
70
  export declare const addMonths: (date: Date, months: number) => Date;
71
+ /**
72
+ * The calendars Date Picker draws with months of their own, when `calendar`
73
+ * names one: the months of the year in Iran, Israel, Saudi Arabia, Ethiopia,
74
+ * Egypt's Coptic church, and India's national calendar. React Aria's
75
+ * `createCalendar` offers the same set.
76
+ */
77
+ export type MonthCalendar = 'coptic' | 'ethiopic' | 'hebrew' | 'indian' | 'islamic-civil' | 'islamic-umalqura' | 'persian';
78
+ /** The `MonthCalendar` names, for a check at run time. */
79
+ export declare const MONTH_CALENDARS: readonly string[];
80
+ /**
81
+ * The months of a calendar, as Date Picker pages through them. Every date is
82
+ * still a Gregorian `Date` at midnight UTC, and a month is the run of days
83
+ * from its first.
84
+ */
85
+ export interface MonthSystem {
86
+ /** A date some months away, its day kept where the month is long enough. */
87
+ add(date: Date, months: number): Date;
88
+ /** The day of its month, from 1. */
89
+ day(date: Date): number;
90
+ /** The month a date is in, as `month` writes it: `YYYY-MM`, or its first day where the months are the calendar's own. */
91
+ key(date: Date): string;
92
+ /** Whether two dates fall in one month. */
93
+ same(left: Date, right: Date): boolean;
94
+ /** The first day of the month a date is in. */
95
+ start(date: Date): Date;
96
+ }
97
+ /**
98
+ * The months a calendar pages through: its own for a `MonthCalendar` name,
99
+ * and the Gregorian months for every other.
100
+ */
101
+ export declare const monthSystem: (calendar?: string) => MonthSystem;
57
102
  /** What bars a date, besides `min` and `max`. */
58
103
  export interface Barring {
59
104
  /** Dates as `YYYY-MM-DD`, separated by commas or spaces. */
@@ -5,6 +5,34 @@ export const localToday = (now = new Date()) => {
5
5
  const day = `${now.getDate()}`.padStart(2, '0');
6
6
  return `${now.getFullYear()}-${month}-${day}`;
7
7
  };
8
+ export const todayIn = (timeZone = '', now = new Date()) => {
9
+ if (timeZone === '') {
10
+ return localToday(now);
11
+ }
12
+ try {
13
+ const parts = new Intl.DateTimeFormat('en-US', {
14
+ day: '2-digit',
15
+ month: '2-digit',
16
+ timeZone,
17
+ year: 'numeric',
18
+ }).formatToParts(now);
19
+ const part = (type) => parts.find((one) => one.type === type)?.value ?? '';
20
+ return `${part('year').padStart(4, '0')}-${part('month')}-${part('day')}`;
21
+ }
22
+ catch {
23
+ return localToday(now);
24
+ }
25
+ };
26
+ export const weekendOf = (locale) => {
27
+ try {
28
+ const tag = new Intl.Locale(locale);
29
+ const days = tag.getWeekInfo?.().weekend ?? tag.weekInfo?.weekend;
30
+ return days === undefined ? [0, 6] : days.map((day) => day % 7);
31
+ }
32
+ catch {
33
+ return [0, 6];
34
+ }
35
+ };
8
36
  export const weekStartOf = (locale) => {
9
37
  try {
10
38
  const tag = new Intl.Locale(locale);
@@ -40,6 +68,80 @@ export const addMonths = (date, months) => {
40
68
  const last = utcDate(target.getUTCFullYear(), target.getUTCMonth() + 1, 0).getUTCDate();
41
69
  return utcDate(target.getUTCFullYear(), target.getUTCMonth(), Math.min(date.getUTCDate(), last));
42
70
  };
71
+ export const MONTH_CALENDARS = [
72
+ 'coptic',
73
+ 'ethiopic',
74
+ 'hebrew',
75
+ 'indian',
76
+ 'islamic-civil',
77
+ 'islamic-umalqura',
78
+ 'persian',
79
+ ];
80
+ const GREGORIAN = {
81
+ add: (date, months) => addMonths(date, months),
82
+ day: (date) => date.getUTCDate(),
83
+ key: (date) => monthOf(date),
84
+ same: (left, right) => sameMonth(left, right),
85
+ start: (date) => utcDate(date.getUTCFullYear(), date.getUTCMonth(), 1),
86
+ };
87
+ const intlMonths = (calendar) => {
88
+ const format = new Intl.DateTimeFormat(`en-u-ca-${calendar}-nu-latn`, {
89
+ day: 'numeric',
90
+ timeZone: 'UTC',
91
+ });
92
+ const days = new Map();
93
+ const day = (date) => {
94
+ const at = date.getTime();
95
+ let found = days.get(at);
96
+ if (found === undefined) {
97
+ found = Number(format.formatToParts(date).find((part) => part.type === 'day')?.value);
98
+ if (days.size > 4000) {
99
+ days.clear();
100
+ }
101
+ days.set(at, found);
102
+ }
103
+ return found;
104
+ };
105
+ const start = (date) => addDays(date, 1 - day(date));
106
+ const length = (first) => {
107
+ for (const count of [29, 30, 31, 28, 32]) {
108
+ if (day(addDays(first, count)) === 1 && day(addDays(first, count - 1)) === count) {
109
+ return count;
110
+ }
111
+ }
112
+ for (let count = 1; count < 28; count += 1) {
113
+ if (day(addDays(first, count)) === 1) {
114
+ return count;
115
+ }
116
+ }
117
+ return 30;
118
+ };
119
+ return {
120
+ add: (date, months) => {
121
+ let first = start(date);
122
+ for (let step = 0; step < Math.abs(months); step += 1) {
123
+ first = months > 0 ? addDays(first, length(first)) : start(addDays(first, -1));
124
+ }
125
+ return addDays(first, Math.min(day(date), length(first)) - 1);
126
+ },
127
+ day,
128
+ key: (date) => fromDate(start(date)),
129
+ same: (left, right) => start(left).getTime() === start(right).getTime(),
130
+ start,
131
+ };
132
+ };
133
+ const systems = new Map();
134
+ export const monthSystem = (calendar = '') => {
135
+ if (!MONTH_CALENDARS.includes(calendar)) {
136
+ return GREGORIAN;
137
+ }
138
+ let system = systems.get(calendar);
139
+ if (system === undefined) {
140
+ system = intlMonths(calendar);
141
+ systems.set(calendar, system);
142
+ }
143
+ return system;
144
+ };
43
145
  export const barredBy = (date, rules) => {
44
146
  const iso = fromDate(date);
45
147
  if (writtenList(rules.disableDates).includes(iso) ||
@@ -7,12 +7,20 @@
7
7
  * expect, since full saturation in HSL is a mid-lightness band rather than a
8
8
  * corner.
9
9
  *
10
- * **Every syntax here is read; three are written.** A field that refuses
10
+ * **Every syntax here is read and written.** A field that refuses
11
11
  * `rgb(255 0 0)` because it prefers hex is a field arguing with the person using
12
- * it, so the space-separated forms, the comma forms, `rgba` and `hsla` are all
13
- * taken. What is written back is whichever of hex, rgb or hsl the component is
14
- * set to, so a value stays in the shape the consumer asked for however it was
15
- * entered.
12
+ * it, so the space-separated forms, the comma forms, `rgba`, `hsla`, `hsba` and
13
+ * `oklch` are all taken. What is written back is whichever of hex, rgb, hsl,
14
+ * hsb or oklch the component is set to, so a value stays in the shape the
15
+ * consumer asked for however it was entered.
16
+ *
17
+ * **`oklch` is read into sRGB.** The working model is sRGB, so a color outside
18
+ * it, such as a display-P3 green written in `oklch`, is clipped to the nearest
19
+ * sRGB channel values. What is written in `oklch` is that sRGB color, exactly.
20
+ *
21
+ * `hsb()` is not CSS. It is the working model written out, as React Aria's and
22
+ * Zag's color strings write it, for a value handed to code that thinks in hue,
23
+ * saturation and brightness. A stylesheet wants one of the other three.
16
24
  *
17
25
  * **What is written is the comma form.** Both are valid, and the commas are what
18
26
  * a reader recognizes, what most code around them holds, and what pastes into
@@ -40,7 +48,7 @@ export interface Color {
40
48
  saturation: number;
41
49
  }
42
50
  /** The syntaxes a value can be written in. */
43
- export type ColorFormat = 'hex' | 'hsl' | 'rgb';
51
+ export type ColorFormat = 'hex' | 'hsb' | 'hsl' | 'oklch' | 'rgb';
44
52
  /** HSB to bytes, which is the road every written syntax goes down. */
45
53
  export declare const toRgb: (color: Color) => {
46
54
  blue: number;
@@ -53,6 +61,15 @@ export declare const toHsl: (color: Color) => {
53
61
  lightness: number;
54
62
  saturation: number;
55
63
  };
64
+ /**
65
+ * Bytes to OKLCH: lightness 0 to 1, chroma from 0, hue in degrees. Björn
66
+ * Ottosson's OKLab matrices, as CSS Color 4 defines `oklch()`.
67
+ */
68
+ export declare const toOklch: (color: Color) => {
69
+ chroma: number;
70
+ hue: number;
71
+ lightness: number;
72
+ };
56
73
  /**
57
74
  * Any of the syntaxes to the working model, or `null` where it is not a color.
58
75
  *
@@ -74,6 +74,40 @@ export const toHsl = (color) => {
74
74
  : (brightness - lightness) / Math.min(lightness, 1 - lightness)) * 100),
75
75
  };
76
76
  };
77
+ const linear = (channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
78
+ const gamma = (channel) => channel <= 0.0031308 ? channel * 12.92 : 1.055 * channel ** (1 / 2.4) - 0.055;
79
+ export const toOklch = (color) => {
80
+ const { blue, green, red } = toRgb(color);
81
+ const [r, g, b] = [red, green, blue].map((one) => linear(one / 255));
82
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
83
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
84
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
85
+ const lightness = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
86
+ const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
87
+ const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
88
+ const chroma = Math.hypot(a, bb);
89
+ const hue = chroma < 0.0001 ? 0 : ((((Math.atan2(bb, a) * 180) / Math.PI) % 360) + 360) % 360;
90
+ return { chroma, hue, lightness };
91
+ };
92
+ const oklchToRgb = (lightness, chroma, hue) => {
93
+ const angle = (hue * Math.PI) / 180;
94
+ const a = chroma * Math.cos(angle);
95
+ const b = chroma * Math.sin(angle);
96
+ const l = (lightness + 0.3963377774 * a + 0.2158037573 * b) ** 3;
97
+ const m = (lightness - 0.1055613458 * a - 0.0638541728 * b) ** 3;
98
+ const s = (lightness - 0.0894841775 * a - 1.291485548 * b) ** 3;
99
+ return [
100
+ 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
101
+ -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
102
+ -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
103
+ ].map((one) => Math.round(clamp(gamma(clamp(one, 0, 1)), 0, 1) * 255));
104
+ };
105
+ const oklchParts = (first, second, third) => {
106
+ const number = (token) => (token === 'none' ? 0 : Number.parseFloat(token));
107
+ const lightness = first.endsWith('%') ? number(first) / 100 : number(first);
108
+ const chroma = second.endsWith('%') ? (number(second) / 100) * 0.4 : number(second);
109
+ return [clamp(lightness, 0, 1), Math.max(0, chroma), number(third)];
110
+ };
77
111
  export const parseColor = (value) => {
78
112
  const text = value.trim().toLowerCase();
79
113
  if (text === '') {
@@ -94,7 +128,7 @@ export const parseColor = (value) => {
94
128
  const carriesAlpha = digits.length === 4 || digits.length === 8;
95
129
  return rgbToHsb(channel(0), channel(1), channel(2), carriesAlpha ? Math.round((channel(3) / 255) * 100) : 100);
96
130
  }
97
- const call = /^(rgba?|hsla?)\(([^)]*)\)$/.exec(text);
131
+ const call = /^(rgba?|hsla?|hsba?|oklch)\(([^)]*)\)$/.exec(text);
98
132
  if (!call?.[1] || call[2] === undefined) {
99
133
  return null;
100
134
  }
@@ -109,12 +143,27 @@ export const parseColor = (value) => {
109
143
  if (call[1].startsWith('rgb')) {
110
144
  return rgbToHsb(toByte(first), toByte(second), toByte(third), alpha);
111
145
  }
146
+ if (call[1] === 'oklch') {
147
+ const parts = oklchParts(first, second, third);
148
+ if (parts.some(Number.isNaN)) {
149
+ return null;
150
+ }
151
+ return rgbToHsb(...oklchToRgb(...parts), alpha);
152
+ }
112
153
  const hue = Number.parseFloat(first);
113
154
  const saturation = clamp(Number.parseFloat(second), 0, 100) / 100;
114
155
  const lightness = clamp(Number.parseFloat(third), 0, 100) / 100;
115
156
  if (Number.isNaN(hue) || Number.isNaN(saturation) || Number.isNaN(lightness)) {
116
157
  return null;
117
158
  }
159
+ if (call[1].startsWith('hsb')) {
160
+ return {
161
+ alpha,
162
+ brightness: Math.round(lightness * 100),
163
+ hue: Math.round(((hue % 360) + 360) % 360),
164
+ saturation: Math.round(saturation * 100),
165
+ };
166
+ }
118
167
  const brightness = lightness + saturation * Math.min(lightness, 1 - lightness);
119
168
  return {
120
169
  alpha,
@@ -126,6 +175,15 @@ export const parseColor = (value) => {
126
175
  export const formatColor = (color, format, withAlpha) => {
127
176
  const alpha = clamp(color.alpha, 0, 100) / 100;
128
177
  const opacity = Number(alpha.toFixed(2));
178
+ if (format === 'oklch') {
179
+ const { chroma, hue, lightness } = toOklch(color);
180
+ const body = `${Number((lightness * 100).toFixed(1))}% ${Number(chroma.toFixed(3))} ${Number(hue.toFixed(1))}`;
181
+ return withAlpha ? `oklch(${body} / ${opacity})` : `oklch(${body})`;
182
+ }
183
+ if (format === 'hsb') {
184
+ const body = `${Math.round(((color.hue % 360) + 360) % 360)}, ${Math.round(clamp(color.saturation, 0, 100))}%, ${Math.round(clamp(color.brightness, 0, 100))}%`;
185
+ return withAlpha ? `hsba(${body}, ${opacity})` : `hsb(${body})`;
186
+ }
129
187
  if (format === 'hsl') {
130
188
  const { hue, lightness, saturation } = toHsl(color);
131
189
  const body = `${Math.round(hue)}, ${saturation}%, ${lightness}%`;
@@ -22,6 +22,8 @@ export type RuntimeKind =
22
22
  'cite-not-url'
23
23
  /** A deprecated property, attribute or slot was used; the message names what replaces it. */
24
24
  | 'deprecated'
25
+ /** Two landmarks of one kind share a name, so a screen reader lists them alike. */
26
+ | 'duplicate-name'
25
27
  /** Another class asked for a tag that is already registered: two copies of Helix. */
26
28
  | 'duplicate-registration'
27
29
  /** The browser refused fullscreen, usually for a frame without the permission. */
package/dist/version.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * release, so the two never disagree; a spec beside this holds them to it.
7
7
  * Nothing else in the running code knows which release it is.
8
8
  */
9
- export declare const VERSION = "0.154.0";
9
+ export declare const VERSION = "0.156.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.154.0';
1
+ export const VERSION = '0.156.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-core",
3
3
  "description": "Helix element primitives: base classes, mixins, decorators, utilities.",
4
- "version": "0.154.0",
4
+ "version": "0.156.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -56,11 +56,11 @@
56
56
  "bugs": "https://github.com/project-fusion/fusion/issues",
57
57
  "dependencies": {
58
58
  "@floating-ui/dom": "^1.8.0",
59
- "@fusion.dev/helix-icons": "0.154.0"
59
+ "@fusion.dev/helix-icons": "0.156.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@fusion.dev/helix-cem": "0.154.0",
63
- "@fusion.dev/helix-testing": "0.154.0",
62
+ "@fusion.dev/helix-cem": "0.156.0",
63
+ "@fusion.dev/helix-testing": "0.156.0",
64
64
  "@lit-labs/ssr": "^4.1.0",
65
65
  "lit": "^3.2.1"
66
66
  },