@fusion.dev/helix-core 0.154.0 → 0.155.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",
@@ -23727,6 +23783,44 @@
23727
23783
  ],
23728
23784
  "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
23785
  },
23786
+ {
23787
+ "kind": "function",
23788
+ "name": "todayIn",
23789
+ "return": {
23790
+ "type": {
23791
+ "text": "string"
23792
+ }
23793
+ },
23794
+ "parameters": [
23795
+ {
23796
+ "name": "timeZone",
23797
+ "default": "''"
23798
+ },
23799
+ {
23800
+ "name": "now",
23801
+ "default": "new Date()"
23802
+ }
23803
+ ],
23804
+ "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."
23805
+ },
23806
+ {
23807
+ "kind": "function",
23808
+ "name": "weekendOf",
23809
+ "return": {
23810
+ "type": {
23811
+ "text": "number[]"
23812
+ }
23813
+ },
23814
+ "parameters": [
23815
+ {
23816
+ "name": "locale",
23817
+ "type": {
23818
+ "text": "string"
23819
+ }
23820
+ }
23821
+ ],
23822
+ "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."
23823
+ },
23730
23824
  {
23731
23825
  "kind": "function",
23732
23826
  "name": "weekStartOf",
@@ -23857,6 +23951,31 @@
23857
23951
  ],
23858
23952
  "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
23953
  },
23954
+ {
23955
+ "kind": "variable",
23956
+ "name": "MONTH_CALENDARS",
23957
+ "type": {
23958
+ "text": "readonly string[]"
23959
+ },
23960
+ "default": "[ 'coptic', 'ethiopic', 'hebrew', 'indian', 'islamic-civil', 'islamic-umalqura', 'persian', ]",
23961
+ "description": "The `MonthCalendar` names, for a check at run time."
23962
+ },
23963
+ {
23964
+ "kind": "function",
23965
+ "name": "monthSystem",
23966
+ "return": {
23967
+ "type": {
23968
+ "text": "MonthSystem"
23969
+ }
23970
+ },
23971
+ "parameters": [
23972
+ {
23973
+ "name": "calendar",
23974
+ "default": "''"
23975
+ }
23976
+ ],
23977
+ "description": "The months a calendar pages through: its own for a `MonthCalendar` name,\nand the Gregorian months for every other."
23978
+ },
23860
23979
  {
23861
23980
  "kind": "function",
23862
23981
  "name": "barredBy",
@@ -24068,6 +24187,22 @@
24068
24187
  "module": "src/utils/calendar/calendar.ts"
24069
24188
  }
24070
24189
  },
24190
+ {
24191
+ "kind": "js",
24192
+ "name": "todayIn",
24193
+ "declaration": {
24194
+ "name": "todayIn",
24195
+ "module": "src/utils/calendar/calendar.ts"
24196
+ }
24197
+ },
24198
+ {
24199
+ "kind": "js",
24200
+ "name": "weekendOf",
24201
+ "declaration": {
24202
+ "name": "weekendOf",
24203
+ "module": "src/utils/calendar/calendar.ts"
24204
+ }
24205
+ },
24071
24206
  {
24072
24207
  "kind": "js",
24073
24208
  "name": "weekStartOf",
@@ -24116,6 +24251,22 @@
24116
24251
  "module": "src/utils/calendar/calendar.ts"
24117
24252
  }
24118
24253
  },
24254
+ {
24255
+ "kind": "js",
24256
+ "name": "MONTH_CALENDARS",
24257
+ "declaration": {
24258
+ "name": "MONTH_CALENDARS",
24259
+ "module": "src/utils/calendar/calendar.ts"
24260
+ }
24261
+ },
24262
+ {
24263
+ "kind": "js",
24264
+ "name": "monthSystem",
24265
+ "declaration": {
24266
+ "name": "monthSystem",
24267
+ "module": "src/utils/calendar/calendar.ts"
24268
+ }
24269
+ },
24119
24270
  {
24120
24271
  "kind": "js",
24121
24272
  "name": "barredBy",
@@ -25170,6 +25321,24 @@
25170
25321
  ],
25171
25322
  "description": "HSB to HSL, which is a different pair of numbers for the same color."
25172
25323
  },
25324
+ {
25325
+ "kind": "function",
25326
+ "name": "toOklch",
25327
+ "return": {
25328
+ "type": {
25329
+ "text": "{ chroma: number; hue: number; lightness: number }"
25330
+ }
25331
+ },
25332
+ "parameters": [
25333
+ {
25334
+ "name": "color",
25335
+ "type": {
25336
+ "text": "Color"
25337
+ }
25338
+ }
25339
+ ],
25340
+ "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()`."
25341
+ },
25173
25342
  {
25174
25343
  "kind": "function",
25175
25344
  "name": "parseColor",
@@ -25254,6 +25423,14 @@
25254
25423
  "module": "src/utils/css-color/css-color.ts"
25255
25424
  }
25256
25425
  },
25426
+ {
25427
+ "kind": "js",
25428
+ "name": "toOklch",
25429
+ "declaration": {
25430
+ "name": "toOklch",
25431
+ "module": "src/utils/css-color/css-color.ts"
25432
+ }
25433
+ },
25257
25434
  {
25258
25435
  "kind": "js",
25259
25436
  "name": "parseColor",
@@ -32230,7 +32407,7 @@
32230
32407
  "type": {
32231
32408
  "text": "string"
32232
32409
  },
32233
- "default": "'0.154.0'",
32410
+ "default": "'0.155.0'",
32234
32411
  "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
32412
  }
32236
32413
  ],
@@ -32790,6 +32967,6 @@
32790
32967
  ],
32791
32968
  "helix": {
32792
32969
  "package": "@fusion.dev/helix-core",
32793
- "version": "0.154.0"
32970
+ "version": "0.155.0"
32794
32971
  }
32795
32972
  }
@@ -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.155.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.154.0';
1
+ export const VERSION = '0.155.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.155.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.155.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.155.0",
63
+ "@fusion.dev/helix-testing": "0.155.0",
64
64
  "@lit-labs/ssr": "^4.1.0",
65
65
  "lit": "^3.2.1"
66
66
  },