@fusion.dev/helix-headless 0.57.0 → 0.58.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +1 -159
- package/dist/color-input/root/color-input-root.d.ts +1 -2
- package/dist/color-input/root/color-input-root.js +1 -2
- package/dist/color-picker/index.d.ts +0 -1
- package/dist/color-picker/index.js +0 -1
- package/dist/color-picker/root/color-picker-root.d.ts +1 -2
- package/dist/color-picker/root/color-picker-root.js +1 -2
- package/dist/date-input/root/date-input-root.js +6 -7
- package/dist/date-picker/root/date-picker-root.js +8 -17
- package/dist/time-picker/root/time-picker-root.d.ts +11 -1
- package/package.json +5 -5
- package/dist/color-picker/color.d.ts +0 -73
- package/dist/color-picker/color.js +0 -168
package/custom-elements.json
CHANGED
|
@@ -18896,169 +18896,11 @@
|
|
|
18896
18896
|
}
|
|
18897
18897
|
]
|
|
18898
18898
|
},
|
|
18899
|
-
{
|
|
18900
|
-
"kind": "javascript-module",
|
|
18901
|
-
"path": "src/color-picker/color.ts",
|
|
18902
|
-
"declarations": [
|
|
18903
|
-
{
|
|
18904
|
-
"kind": "function",
|
|
18905
|
-
"name": "toRgb",
|
|
18906
|
-
"return": {
|
|
18907
|
-
"type": {
|
|
18908
|
-
"text": "{ blue: number; green: number; red: number }"
|
|
18909
|
-
}
|
|
18910
|
-
},
|
|
18911
|
-
"parameters": [
|
|
18912
|
-
{
|
|
18913
|
-
"name": "color",
|
|
18914
|
-
"type": {
|
|
18915
|
-
"text": "Color"
|
|
18916
|
-
}
|
|
18917
|
-
}
|
|
18918
|
-
],
|
|
18919
|
-
"description": "HSB to bytes, which is the road every written syntax goes down."
|
|
18920
|
-
},
|
|
18921
|
-
{
|
|
18922
|
-
"kind": "function",
|
|
18923
|
-
"name": "splitColors",
|
|
18924
|
-
"return": {
|
|
18925
|
-
"type": {
|
|
18926
|
-
"text": "string[]"
|
|
18927
|
-
}
|
|
18928
|
-
},
|
|
18929
|
-
"parameters": [
|
|
18930
|
-
{
|
|
18931
|
-
"name": "value",
|
|
18932
|
-
"type": {
|
|
18933
|
-
"text": "string"
|
|
18934
|
-
}
|
|
18935
|
-
}
|
|
18936
|
-
],
|
|
18937
|
-
"description": "Splits a comma-separated list of colors, ignoring the commas inside them.\n\nA plain `split(',')` cannot hold the legacy syntaxes:\n`#ef4444, rgb(249 115 22), rgba(34,197,94,.5)` comes apart into six fragments,\nfour of them not colors, and the row quietly renders three swatches instead of\nfour. The modern space-separated forms survive it, which is what makes such a\nlist look like it works.\n\nDepth rather than a regular expression, because the thing that matters is\nwhether a comma is inside a function call and that is exactly what a counter\nanswers. No color syntax nests, so a single counter is enough."
|
|
18938
|
-
},
|
|
18939
|
-
{
|
|
18940
|
-
"kind": "function",
|
|
18941
|
-
"name": "parseColor",
|
|
18942
|
-
"return": {
|
|
18943
|
-
"type": {
|
|
18944
|
-
"text": "Color | null"
|
|
18945
|
-
}
|
|
18946
|
-
},
|
|
18947
|
-
"parameters": [
|
|
18948
|
-
{
|
|
18949
|
-
"name": "value",
|
|
18950
|
-
"type": {
|
|
18951
|
-
"text": "string"
|
|
18952
|
-
}
|
|
18953
|
-
}
|
|
18954
|
-
],
|
|
18955
|
-
"description": "Any of the syntaxes to the working model, or `null` where it is not a color.\n\nNamed colors are not here. Resolving `rebeccapurple` means either shipping\nthe table of a hundred and forty or asking the browser, and the browser is not\npresent on the server: a field that accepted a name client-side and rejected\nit during a server render would be worse than one that never took it."
|
|
18956
|
-
},
|
|
18957
|
-
{
|
|
18958
|
-
"kind": "function",
|
|
18959
|
-
"name": "formatColor",
|
|
18960
|
-
"return": {
|
|
18961
|
-
"type": {
|
|
18962
|
-
"text": "string"
|
|
18963
|
-
}
|
|
18964
|
-
},
|
|
18965
|
-
"parameters": [
|
|
18966
|
-
{
|
|
18967
|
-
"name": "color",
|
|
18968
|
-
"type": {
|
|
18969
|
-
"text": "Color"
|
|
18970
|
-
}
|
|
18971
|
-
},
|
|
18972
|
-
{
|
|
18973
|
-
"name": "format",
|
|
18974
|
-
"type": {
|
|
18975
|
-
"text": "ColorFormat"
|
|
18976
|
-
}
|
|
18977
|
-
},
|
|
18978
|
-
{
|
|
18979
|
-
"name": "withAlpha",
|
|
18980
|
-
"type": {
|
|
18981
|
-
"text": "boolean"
|
|
18982
|
-
}
|
|
18983
|
-
}
|
|
18984
|
-
],
|
|
18985
|
-
"description": "The working model back out, in the syntax asked for.\n\nThe alpha is written only where the component carries one. A consumer who did\nnot ask for transparency should not have to strip `ff` off every hex or read\npast a fourth argument they never wanted."
|
|
18986
|
-
},
|
|
18987
|
-
{
|
|
18988
|
-
"kind": "function",
|
|
18989
|
-
"name": "toCss",
|
|
18990
|
-
"return": {
|
|
18991
|
-
"type": {
|
|
18992
|
-
"text": "string"
|
|
18993
|
-
}
|
|
18994
|
-
},
|
|
18995
|
-
"parameters": [
|
|
18996
|
-
{
|
|
18997
|
-
"name": "color",
|
|
18998
|
-
"type": {
|
|
18999
|
-
"text": "Color"
|
|
19000
|
-
}
|
|
19001
|
-
}
|
|
19002
|
-
],
|
|
19003
|
-
"description": "The color as CSS, for the swatches and the tracks. Always with its alpha."
|
|
19004
|
-
}
|
|
19005
|
-
],
|
|
19006
|
-
"exports": [
|
|
19007
|
-
{
|
|
19008
|
-
"kind": "js",
|
|
19009
|
-
"name": "toRgb",
|
|
19010
|
-
"declaration": {
|
|
19011
|
-
"name": "toRgb",
|
|
19012
|
-
"module": "src/color-picker/color.ts"
|
|
19013
|
-
}
|
|
19014
|
-
},
|
|
19015
|
-
{
|
|
19016
|
-
"kind": "js",
|
|
19017
|
-
"name": "splitColors",
|
|
19018
|
-
"declaration": {
|
|
19019
|
-
"name": "splitColors",
|
|
19020
|
-
"module": "src/color-picker/color.ts"
|
|
19021
|
-
}
|
|
19022
|
-
},
|
|
19023
|
-
{
|
|
19024
|
-
"kind": "js",
|
|
19025
|
-
"name": "parseColor",
|
|
19026
|
-
"declaration": {
|
|
19027
|
-
"name": "parseColor",
|
|
19028
|
-
"module": "src/color-picker/color.ts"
|
|
19029
|
-
}
|
|
19030
|
-
},
|
|
19031
|
-
{
|
|
19032
|
-
"kind": "js",
|
|
19033
|
-
"name": "formatColor",
|
|
19034
|
-
"declaration": {
|
|
19035
|
-
"name": "formatColor",
|
|
19036
|
-
"module": "src/color-picker/color.ts"
|
|
19037
|
-
}
|
|
19038
|
-
},
|
|
19039
|
-
{
|
|
19040
|
-
"kind": "js",
|
|
19041
|
-
"name": "toCss",
|
|
19042
|
-
"declaration": {
|
|
19043
|
-
"name": "toCss",
|
|
19044
|
-
"module": "src/color-picker/color.ts"
|
|
19045
|
-
}
|
|
19046
|
-
}
|
|
19047
|
-
]
|
|
19048
|
-
},
|
|
19049
18899
|
{
|
|
19050
18900
|
"kind": "javascript-module",
|
|
19051
18901
|
"path": "src/color-picker/index.ts",
|
|
19052
18902
|
"declarations": [],
|
|
19053
18903
|
"exports": [
|
|
19054
|
-
{
|
|
19055
|
-
"kind": "js",
|
|
19056
|
-
"name": "*",
|
|
19057
|
-
"declaration": {
|
|
19058
|
-
"name": "*",
|
|
19059
|
-
"module": "src/color-picker/color.js"
|
|
19060
|
-
}
|
|
19061
|
-
},
|
|
19062
18904
|
{
|
|
19063
18905
|
"kind": "js",
|
|
19064
18906
|
"name": "*",
|
|
@@ -94129,7 +93971,7 @@
|
|
|
94129
93971
|
}
|
|
94130
93972
|
}
|
|
94131
93973
|
],
|
|
94132
|
-
"description": "Whether a string is a time the rest of this component can reason about."
|
|
93974
|
+
"description": "Whether a string is a time the rest of this component can reason about.\n\n**Narrower than core's `valueAsTime` on purpose, and the difference is\nseconds.** That one takes them because `<input type=\"time\">` does, at a step\nbelow a minute. This library has no such step: `step` here is minutes and is\nfloored at one, so nothing it offers or writes ever carries a second, and\n`toMinutes` below would drop one silently if this let it through. Widening\nthis to the platform's shape would let a value in that the rest of the family\ncannot hold."
|
|
94133
93975
|
},
|
|
94134
93976
|
{
|
|
94135
93977
|
"kind": "function",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { AriaForwardController, HelixFormElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { AriaForwardController, type ColorFormat, HelixFormElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../color-picker/index.js';
|
|
4
|
-
import { type ColorFormat } from '../../color-picker/color.js';
|
|
5
4
|
declare const HelixHeadlessColorInput_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinPickerFieldInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinFieldInterface) & typeof HelixFormElement;
|
|
6
5
|
/**
|
|
7
6
|
* @summary A color, typed in any syntax or picked from a panel.
|
|
@@ -4,12 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, customElement, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerField, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerField, parseColor, toCss, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { html } from 'lit/static-html.js';
|
|
11
11
|
import '../../color-picker/index.js';
|
|
12
|
-
import { parseColor, toCss } from '../../color-picker/color.js';
|
|
13
12
|
let HelixHeadlessColorInput = class HelixHeadlessColorInput extends MixinPickerField(MixinControllerOpen(MixinControllerValue(MixinField(HelixFormElement)))) {
|
|
14
13
|
constructor() {
|
|
15
14
|
super(...arguments);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { HelixFormElement } from '@fusion.dev/helix-core';
|
|
1
|
+
import { type Color, type ColorFormat, HelixFormElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../button/index.js';
|
|
4
4
|
import '../../button-group/index.js';
|
|
5
5
|
import '../../slider/index.js';
|
|
6
|
-
import { type Color, type ColorFormat } from '../color.js';
|
|
7
6
|
declare const HelixHeadlessColorPicker_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerValueInterface) & typeof HelixFormElement;
|
|
8
7
|
/**
|
|
9
8
|
* @summary Picks a color, from a square and two ramps.
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { CopyController, capturePointer, customElement, drawIcon, HelixFormElement, HelixSampleEndEvent, HelixSampleStartEvent, MixinControllerValue, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { CopyController, capturePointer, customElement, drawIcon, formatColor, HelixFormElement, HelixSampleEndEvent, HelixSampleStartEvent, MixinControllerValue, parseColor, splitColors, toCss, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { eyedropper } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -12,7 +12,6 @@ import { html } from 'lit/static-html.js';
|
|
|
12
12
|
import '../../button/index.js';
|
|
13
13
|
import '../../button-group/index.js';
|
|
14
14
|
import '../../slider/index.js';
|
|
15
|
-
import { formatColor, parseColor, splitColors, toCss, } from '../color.js';
|
|
16
15
|
const FORMATS = ['hex', 'rgb', 'hsl'];
|
|
17
16
|
const START = { alpha: 100, brightness: 100, hue: 0, saturation: 100 };
|
|
18
17
|
let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinControllerValue(HelixFormElement) {
|
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, valueAsDate as asDate, customElement, dateAsValue as fromDate, HelixFormElement, isDateValue, MixinControllerOpen, MixinControllerValue, MixinField, MixinPickerEditing, MixinPickerField, MixinTokens, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { calendarBlank } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -12,7 +12,6 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
12
12
|
import { html } from 'lit/static-html.js';
|
|
13
13
|
import '../../date-picker/index.js';
|
|
14
14
|
import '../../tag/index.js';
|
|
15
|
-
const isIso = (value) => /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
16
15
|
const dates = (value) => value
|
|
17
16
|
.replace(/\s+[–-]\s+/g, ',')
|
|
18
17
|
.split(',')
|
|
@@ -47,7 +46,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
|
|
|
47
46
|
return;
|
|
48
47
|
}
|
|
49
48
|
const held = dates(typed);
|
|
50
|
-
const whole = held.length > 0 && held.every(
|
|
49
|
+
const whole = held.length > 0 && held.every(isDateValue);
|
|
51
50
|
this.requestValue(whole ? held.join(',') : typed);
|
|
52
51
|
this.restore();
|
|
53
52
|
};
|
|
@@ -56,7 +55,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
|
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
57
|
const held = dates(this.value);
|
|
59
|
-
if (event.key === 'Enter' &&
|
|
58
|
+
if (event.key === 'Enter' && isDateValue(this.draft)) {
|
|
60
59
|
event.preventDefault();
|
|
61
60
|
if (!held.includes(this.draft)) {
|
|
62
61
|
this.requestValue([...held, this.draft].join(','));
|
|
@@ -107,10 +106,10 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
|
|
|
107
106
|
}
|
|
108
107
|
get picked() {
|
|
109
108
|
const held = dates(this.value);
|
|
110
|
-
return held.length > 0 && held.every(
|
|
109
|
+
return held.length > 0 && held.every(isDateValue) ? held.join(',') : '';
|
|
111
110
|
}
|
|
112
111
|
written(date) {
|
|
113
|
-
if (this.dateStyle === 'iso' || !
|
|
112
|
+
if (this.dateStyle === 'iso' || !isDateValue(date)) {
|
|
114
113
|
return date;
|
|
115
114
|
}
|
|
116
115
|
const at = new Date(`${date}T00:00:00Z`);
|
|
@@ -141,7 +140,7 @@ let HelixHeadlessDateInput = class HelixHeadlessDateInput extends MixinPickerEdi
|
|
|
141
140
|
}
|
|
142
141
|
validate() {
|
|
143
142
|
const held = dates(this.value);
|
|
144
|
-
if (held.length > 0 && !held.every(
|
|
143
|
+
if (held.length > 0 && !held.every(isDateValue)) {
|
|
145
144
|
return this.mode === undefined
|
|
146
145
|
? 'Enter a date as YYYY-MM-DD.'
|
|
147
146
|
: 'Enter dates as YYYY-MM-DD, separated by commas.';
|
|
@@ -21,15 +21,6 @@ const KEYS = new Set([
|
|
|
21
21
|
'PageUp',
|
|
22
22
|
' ',
|
|
23
23
|
]);
|
|
24
|
-
const toUtc = (iso) => {
|
|
25
|
-
const parts = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
26
|
-
if (!parts) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
const date = new Date(Date.UTC(Number(parts[1]), Number(parts[2]) - 1, Number(parts[3])));
|
|
30
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
31
|
-
};
|
|
32
|
-
const toIso = (date) => date.toISOString().slice(0, 10);
|
|
33
24
|
const localToday = () => {
|
|
34
25
|
const now = new Date();
|
|
35
26
|
const month = `${now.getMonth() + 1}`.padStart(2, '0');
|
|
@@ -104,7 +95,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
104
95
|
this.step = 1;
|
|
105
96
|
}
|
|
106
97
|
get month() {
|
|
107
|
-
return this.monthState ?? (this.defaultMonth ||
|
|
98
|
+
return this.monthState ?? (this.defaultMonth || fromDate(this.derivedMonth).slice(0, 7));
|
|
108
99
|
}
|
|
109
100
|
set month(next) {
|
|
110
101
|
const previous = this.month;
|
|
@@ -137,12 +128,12 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
137
128
|
return this.derivedMonth;
|
|
138
129
|
}
|
|
139
130
|
get derivedMonth() {
|
|
140
|
-
const from =
|
|
131
|
+
const from = asDate(this.selected[0] ?? '') ?? new Date();
|
|
141
132
|
return new Date(Date.UTC(from.getUTCFullYear(), from.getUTCMonth(), 1));
|
|
142
133
|
}
|
|
143
134
|
get anchor() {
|
|
144
135
|
const shown = this.shown;
|
|
145
|
-
for (const candidate of [
|
|
136
|
+
for (const candidate of [asDate(this.focused), asDate(this.selected[0] ?? '')]) {
|
|
146
137
|
if (candidate && sameMonth(candidate, shown)) {
|
|
147
138
|
return candidate;
|
|
148
139
|
}
|
|
@@ -155,7 +146,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
155
146
|
return Array.from({ length: 6 }, (_, week) => Array.from({ length: 7 }, (_, day) => addDays(start, week * 7 + day)));
|
|
156
147
|
}
|
|
157
148
|
barred(date) {
|
|
158
|
-
const iso =
|
|
149
|
+
const iso = fromDate(date);
|
|
159
150
|
if (this.min !== '' && iso < this.min) {
|
|
160
151
|
return true;
|
|
161
152
|
}
|
|
@@ -195,7 +186,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
195
186
|
});
|
|
196
187
|
}
|
|
197
188
|
moveTo(date) {
|
|
198
|
-
this.focused =
|
|
189
|
+
this.focused = fromDate(date);
|
|
199
190
|
this.show(date);
|
|
200
191
|
this.updateComplete.then(() => {
|
|
201
192
|
this.renderRoot.querySelector('[part~="cell"][tabindex="0"]')?.focus();
|
|
@@ -208,7 +199,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
208
199
|
if (this.barred(date)) {
|
|
209
200
|
return;
|
|
210
201
|
}
|
|
211
|
-
const iso =
|
|
202
|
+
const iso = fromDate(date);
|
|
212
203
|
const held = this.selected;
|
|
213
204
|
let next;
|
|
214
205
|
if (this.mode === 'range') {
|
|
@@ -309,7 +300,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
309
300
|
renderMonth(month, index) {
|
|
310
301
|
const { full, heading, weekday } = this.formats;
|
|
311
302
|
const weeks = this.weeksOf(month);
|
|
312
|
-
const anchor =
|
|
303
|
+
const anchor = fromDate(this.anchor);
|
|
313
304
|
const today = localToday();
|
|
314
305
|
const range = this.range;
|
|
315
306
|
const first = index === 0;
|
|
@@ -349,7 +340,7 @@ let HelixHeadlessDatePicker = class HelixHeadlessDatePicker extends MixinControl
|
|
|
349
340
|
</div>
|
|
350
341
|
${weeks.map((week) => html `<div part="week" role="row">
|
|
351
342
|
${week.map((date) => {
|
|
352
|
-
const iso =
|
|
343
|
+
const iso = fromDate(date);
|
|
353
344
|
const disabled = this.barred(date);
|
|
354
345
|
const selected = this.isSelected(iso);
|
|
355
346
|
const outside = !sameMonth(date, month);
|
|
@@ -2,7 +2,17 @@ import { HelixFormElement } from '@fusion.dev/helix-core';
|
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
import '../../listbox/index.js';
|
|
4
4
|
import '../../option/index.js';
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Whether a string is a time the rest of this component can reason about.
|
|
7
|
+
*
|
|
8
|
+
* **Narrower than core's `valueAsTime` on purpose, and the difference is
|
|
9
|
+
* seconds.** That one takes them because `<input type="time">` does, at a step
|
|
10
|
+
* below a minute. This library has no such step: `step` here is minutes and is
|
|
11
|
+
* floored at one, so nothing it offers or writes ever carries a second, and
|
|
12
|
+
* `toMinutes` below would drop one silently if this let it through. Widening
|
|
13
|
+
* this to the platform's shape would let a value in that the rest of the family
|
|
14
|
+
* cannot hold.
|
|
15
|
+
*/
|
|
6
16
|
export declare const isTime: (value: string) => boolean;
|
|
7
17
|
/** Minutes since midnight, or `null` where it is not a time. */
|
|
8
18
|
export declare const toMinutes: (value: string) => number | null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusion.dev/helix-headless",
|
|
3
3
|
"description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.58.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
},
|
|
47
47
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@fusion.dev/helix-core": "0.
|
|
50
|
-
"@fusion.dev/helix-icons": "0.
|
|
49
|
+
"@fusion.dev/helix-core": "0.58.1",
|
|
50
|
+
"@fusion.dev/helix-icons": "0.58.1",
|
|
51
51
|
"uqr": "^0.1.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
55
|
-
"@fusion.dev/helix-cem": "0.
|
|
56
|
-
"@fusion.dev/helix-testing": "0.
|
|
55
|
+
"@fusion.dev/helix-cem": "0.58.1",
|
|
56
|
+
"@fusion.dev/helix-testing": "0.58.1",
|
|
57
57
|
"@fusion.dev/scripts": "0.0.0-dev",
|
|
58
58
|
"@happy-dom/global-registrator": "^20.0.0",
|
|
59
59
|
"@lit-labs/ssr": "^4.1.0",
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading and writing the color syntaxes a field is allowed to hold.
|
|
3
|
-
*
|
|
4
|
-
* **The working model is HSB**, hue and saturation and brightness, because that
|
|
5
|
-
* is what the panel is: a square of saturation against brightness with a hue
|
|
6
|
-
* ramp beside it. HSL would put the square's corners somewhere a reader does not
|
|
7
|
-
* expect, since full saturation in HSL is a mid-lightness band rather than a
|
|
8
|
-
* corner.
|
|
9
|
-
*
|
|
10
|
-
* **Every syntax here is read; three are written.** A field that refuses
|
|
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.
|
|
16
|
-
*
|
|
17
|
-
* **What is written is the comma form.** Both are valid, and the commas are what
|
|
18
|
-
* a reader recognizes, what most code around them holds, and what pastes into
|
|
19
|
-
* anything without a second thought. A syntax a value is handed *out* in is not
|
|
20
|
-
* the place to be early. `rgba` and `hsla` come with them, since a comma form
|
|
21
|
-
* with a fourth argument is spelled that way.
|
|
22
|
-
*
|
|
23
|
-
* Its own module rather than sitting in the element, because it is arithmetic
|
|
24
|
-
* with no DOM in it: this is the half worth testing directly.
|
|
25
|
-
*/
|
|
26
|
-
/** A color as the panel holds it. Alpha and the two percentages are 0 to 100. */
|
|
27
|
-
export interface Color {
|
|
28
|
-
alpha: number;
|
|
29
|
-
brightness: number;
|
|
30
|
-
hue: number;
|
|
31
|
-
saturation: number;
|
|
32
|
-
}
|
|
33
|
-
/** The syntaxes a value can be written in. */
|
|
34
|
-
export type ColorFormat = 'hex' | 'hsl' | 'rgb';
|
|
35
|
-
/** HSB to bytes, which is the road every written syntax goes down. */
|
|
36
|
-
export declare const toRgb: (color: Color) => {
|
|
37
|
-
blue: number;
|
|
38
|
-
green: number;
|
|
39
|
-
red: number;
|
|
40
|
-
};
|
|
41
|
-
/**
|
|
42
|
-
* Splits a comma-separated list of colors, ignoring the commas inside them.
|
|
43
|
-
*
|
|
44
|
-
* A plain `split(',')` cannot hold the legacy syntaxes:
|
|
45
|
-
* `#ef4444, rgb(249 115 22), rgba(34,197,94,.5)` comes apart into six fragments,
|
|
46
|
-
* four of them not colors, and the row quietly renders three swatches instead of
|
|
47
|
-
* four. The modern space-separated forms survive it, which is what makes such a
|
|
48
|
-
* list look like it works.
|
|
49
|
-
*
|
|
50
|
-
* Depth rather than a regular expression, because the thing that matters is
|
|
51
|
-
* whether a comma is inside a function call and that is exactly what a counter
|
|
52
|
-
* answers. No color syntax nests, so a single counter is enough.
|
|
53
|
-
*/
|
|
54
|
-
export declare const splitColors: (value: string) => string[];
|
|
55
|
-
/**
|
|
56
|
-
* Any of the syntaxes to the working model, or `null` where it is not a color.
|
|
57
|
-
*
|
|
58
|
-
* Named colors are not here. Resolving `rebeccapurple` means either shipping
|
|
59
|
-
* the table of a hundred and forty or asking the browser, and the browser is not
|
|
60
|
-
* present on the server: a field that accepted a name client-side and rejected
|
|
61
|
-
* it during a server render would be worse than one that never took it.
|
|
62
|
-
*/
|
|
63
|
-
export declare const parseColor: (value: string) => Color | null;
|
|
64
|
-
/**
|
|
65
|
-
* The working model back out, in the syntax asked for.
|
|
66
|
-
*
|
|
67
|
-
* The alpha is written only where the component carries one. A consumer who did
|
|
68
|
-
* not ask for transparency should not have to strip `ff` off every hex or read
|
|
69
|
-
* past a fourth argument they never wanted.
|
|
70
|
-
*/
|
|
71
|
-
export declare const formatColor: (color: Color, format: ColorFormat, withAlpha: boolean) => string;
|
|
72
|
-
/** The color as CSS, for the swatches and the tracks. Always with its alpha. */
|
|
73
|
-
export declare const toCss: (color: Color) => string;
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
2
|
-
const toAlpha = (token) => {
|
|
3
|
-
if (token === undefined) {
|
|
4
|
-
return 100;
|
|
5
|
-
}
|
|
6
|
-
const number = Number.parseFloat(token);
|
|
7
|
-
if (Number.isNaN(number)) {
|
|
8
|
-
return 100;
|
|
9
|
-
}
|
|
10
|
-
return clamp(token.includes('%') ? number : number * 100, 0, 100);
|
|
11
|
-
};
|
|
12
|
-
const toByte = (token) => {
|
|
13
|
-
const number = Number.parseFloat(token);
|
|
14
|
-
return clamp(token.includes('%') ? (number / 100) * 255 : number, 0, 255);
|
|
15
|
-
};
|
|
16
|
-
const rgbToHsb = (red, green, blue, alpha) => {
|
|
17
|
-
const r = red / 255;
|
|
18
|
-
const g = green / 255;
|
|
19
|
-
const b = blue / 255;
|
|
20
|
-
const high = Math.max(r, g, b);
|
|
21
|
-
const low = Math.min(r, g, b);
|
|
22
|
-
const span = high - low;
|
|
23
|
-
let hue = 0;
|
|
24
|
-
if (span !== 0) {
|
|
25
|
-
if (high === r) {
|
|
26
|
-
hue = (g - b) / span + (g < b ? 6 : 0);
|
|
27
|
-
}
|
|
28
|
-
else if (high === g) {
|
|
29
|
-
hue = (b - r) / span + 2;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
hue = (r - g) / span + 4;
|
|
33
|
-
}
|
|
34
|
-
hue *= 60;
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
alpha,
|
|
38
|
-
brightness: Math.round(high * 100),
|
|
39
|
-
hue: Math.round(hue),
|
|
40
|
-
saturation: Math.round(high === 0 ? 0 : (span / high) * 100),
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
export const toRgb = (color) => {
|
|
44
|
-
const hue = (((color.hue % 360) + 360) % 360) / 60;
|
|
45
|
-
const saturation = color.saturation / 100;
|
|
46
|
-
const brightness = color.brightness / 100;
|
|
47
|
-
const chroma = brightness * saturation;
|
|
48
|
-
const middle = chroma * (1 - Math.abs((hue % 2) - 1));
|
|
49
|
-
const base = brightness - chroma;
|
|
50
|
-
const sextants = [
|
|
51
|
-
[chroma, middle, 0],
|
|
52
|
-
[middle, chroma, 0],
|
|
53
|
-
[0, chroma, middle],
|
|
54
|
-
[0, middle, chroma],
|
|
55
|
-
[middle, 0, chroma],
|
|
56
|
-
[chroma, 0, middle],
|
|
57
|
-
];
|
|
58
|
-
const [red, green, blue] = sextants[Math.floor(hue) % 6] ?? [0, 0, 0];
|
|
59
|
-
return {
|
|
60
|
-
blue: Math.round((blue + base) * 255),
|
|
61
|
-
green: Math.round((green + base) * 255),
|
|
62
|
-
red: Math.round((red + base) * 255),
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
const toHsl = (color) => {
|
|
66
|
-
const saturation = color.saturation / 100;
|
|
67
|
-
const brightness = color.brightness / 100;
|
|
68
|
-
const lightness = (brightness * (2 - saturation)) / 2;
|
|
69
|
-
return {
|
|
70
|
-
hue: ((color.hue % 360) + 360) % 360,
|
|
71
|
-
lightness: Math.round(lightness * 100),
|
|
72
|
-
saturation: Math.round((lightness === 0 || lightness === 1
|
|
73
|
-
? 0
|
|
74
|
-
: (brightness - lightness) / Math.min(lightness, 1 - lightness)) * 100),
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
export const splitColors = (value) => {
|
|
78
|
-
const found = [];
|
|
79
|
-
let depth = 0;
|
|
80
|
-
let current = '';
|
|
81
|
-
for (const character of value) {
|
|
82
|
-
if (character === '(') {
|
|
83
|
-
depth += 1;
|
|
84
|
-
}
|
|
85
|
-
else if (character === ')') {
|
|
86
|
-
depth = Math.max(0, depth - 1);
|
|
87
|
-
}
|
|
88
|
-
if (character === ',' && depth === 0) {
|
|
89
|
-
found.push(current.trim());
|
|
90
|
-
current = '';
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
current += character;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
found.push(current.trim());
|
|
97
|
-
return found.filter(Boolean);
|
|
98
|
-
};
|
|
99
|
-
export const parseColor = (value) => {
|
|
100
|
-
const text = value.trim().toLowerCase();
|
|
101
|
-
if (text === '') {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
if (text.startsWith('#')) {
|
|
105
|
-
const digits = text.slice(1);
|
|
106
|
-
const short = digits.length === 3 || digits.length === 4;
|
|
107
|
-
const long = digits.length === 6 || digits.length === 8;
|
|
108
|
-
if ((!short && !long) || !/^[\da-f]+$/.test(digits)) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
const width = short ? 1 : 2;
|
|
112
|
-
const channel = (index) => {
|
|
113
|
-
const piece = digits.slice(index * width, index * width + width);
|
|
114
|
-
return Number.parseInt(short ? piece + piece : piece, 16);
|
|
115
|
-
};
|
|
116
|
-
const carriesAlpha = digits.length === 4 || digits.length === 8;
|
|
117
|
-
return rgbToHsb(channel(0), channel(1), channel(2), carriesAlpha ? Math.round((channel(3) / 255) * 100) : 100);
|
|
118
|
-
}
|
|
119
|
-
const call = /^(rgba?|hsla?)\(([^)]*)\)$/.exec(text);
|
|
120
|
-
if (!call?.[1] || call[2] === undefined) {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
const [first = '', second = '', third = '', fourth] = call[2]
|
|
124
|
-
.replace(/[,/]/g, ' ')
|
|
125
|
-
.split(/\s+/)
|
|
126
|
-
.filter(Boolean);
|
|
127
|
-
if (first === '' || second === '' || third === '') {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
const alpha = toAlpha(fourth);
|
|
131
|
-
if (call[1].startsWith('rgb')) {
|
|
132
|
-
return rgbToHsb(toByte(first), toByte(second), toByte(third), alpha);
|
|
133
|
-
}
|
|
134
|
-
const hue = Number.parseFloat(first);
|
|
135
|
-
const saturation = clamp(Number.parseFloat(second), 0, 100) / 100;
|
|
136
|
-
const lightness = clamp(Number.parseFloat(third), 0, 100) / 100;
|
|
137
|
-
if (Number.isNaN(hue) || Number.isNaN(saturation) || Number.isNaN(lightness)) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
const brightness = lightness + saturation * Math.min(lightness, 1 - lightness);
|
|
141
|
-
return {
|
|
142
|
-
alpha,
|
|
143
|
-
brightness: Math.round(brightness * 100),
|
|
144
|
-
hue: Math.round(((hue % 360) + 360) % 360),
|
|
145
|
-
saturation: Math.round((brightness === 0 ? 0 : 2 * (1 - lightness / brightness)) * 100),
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
export const formatColor = (color, format, withAlpha) => {
|
|
149
|
-
const alpha = clamp(color.alpha, 0, 100) / 100;
|
|
150
|
-
const opacity = Number(alpha.toFixed(2));
|
|
151
|
-
if (format === 'hsl') {
|
|
152
|
-
const { hue, lightness, saturation } = toHsl(color);
|
|
153
|
-
const body = `${Math.round(hue)}, ${saturation}%, ${lightness}%`;
|
|
154
|
-
return withAlpha ? `hsla(${body}, ${opacity})` : `hsl(${body})`;
|
|
155
|
-
}
|
|
156
|
-
const { blue, green, red } = toRgb(color);
|
|
157
|
-
if (format === 'rgb') {
|
|
158
|
-
const body = `${red}, ${green}, ${blue}`;
|
|
159
|
-
return withAlpha ? `rgba(${body}, ${opacity})` : `rgb(${body})`;
|
|
160
|
-
}
|
|
161
|
-
const byte = (one) => one.toString(16).padStart(2, '0');
|
|
162
|
-
const hex = `#${byte(red)}${byte(green)}${byte(blue)}`;
|
|
163
|
-
return withAlpha ? hex + byte(Math.round(alpha * 255)) : hex;
|
|
164
|
-
};
|
|
165
|
-
export const toCss = (color) => {
|
|
166
|
-
const { blue, green, red } = toRgb(color);
|
|
167
|
-
return `rgb(${red} ${green} ${blue} / ${clamp(color.alpha, 0, 100)}%)`;
|
|
168
|
-
};
|