@fusion.dev/helix-headless 0.47.1 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +149 -39
- package/dist/color-picker/root/color-picker-root.d.ts +78 -3
- package/dist/color-picker/root/color-picker-root.js +47 -4
- package/dist/outline/root/outline-root.d.ts +1 -1
- package/dist/rating/root/rating-root.d.ts +65 -7
- package/dist/rating/root/rating-root.js +35 -8
- package/dist/tabs/panel/tab-panel.d.ts +1 -1
- package/dist/tabs/root/tabs-root.d.ts +1 -1
- package/dist/tabs/tab/tab.d.ts +1 -1
- package/dist/time-picker/root/time-picker-root.d.ts +13 -0
- package/dist/time-picker/root/time-picker-root.js +2 -0
- package/dist/tour/root/tour-root.d.ts +1 -1
- package/dist/tour/stop/tour-stop.d.ts +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +377 -22
- package/types/vue.d.ts +99 -4
|
@@ -29,6 +29,7 @@ declare const HelixHeadlessRating_base: (new (...args: any[]) => import("@fusion
|
|
|
29
29
|
* @event {Event} input - Announced once the value has moved.
|
|
30
30
|
*
|
|
31
31
|
* @csspart feedback - What is wrong with it.
|
|
32
|
+
* @csspart field - The row: the marks, and the score beside them.
|
|
32
33
|
* @csspart fill - The filled marks, clipped to the score.
|
|
33
34
|
* @csspart hint - Help under the marks, while nothing is wrong.
|
|
34
35
|
* @csspart label - The question, over the marks.
|
|
@@ -40,8 +41,8 @@ declare const HelixHeadlessRating_base: (new (...args: any[]) => import("@fusion
|
|
|
40
41
|
*
|
|
41
42
|
* @cssproperty --rating-full - How many marks are wholly filled.
|
|
42
43
|
* @cssproperty --rating-part - How much of the next mark is filled, 0 to 1.
|
|
43
|
-
* @cssproperty --rating-step -
|
|
44
|
-
* @cssproperty --rating-steps - How many steps
|
|
44
|
+
* @cssproperty --rating-step - Which step along the row a target is, counting from one, whatever `min` leaves open.
|
|
45
|
+
* @cssproperty --rating-steps - How many steps the whole row holds, which is `max` over `step`.
|
|
45
46
|
*
|
|
46
47
|
* @cssstate invalid - While there is feedback to show: the platform's verdict, or the consumer's `feedback`.
|
|
47
48
|
*/
|
|
@@ -58,6 +59,40 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
|
|
|
58
59
|
* different answers and a control that cannot express the first has to
|
|
59
60
|
* pretend the second. A survey that requires an answer says so with
|
|
60
61
|
* `required`, not by making one unreachable.
|
|
62
|
+
*
|
|
63
|
+
* **It is the floor of the scale, and every edge follows from that.**
|
|
64
|
+
*
|
|
65
|
+
* *Clearing* returns the row to it. An empty rating, a value nothing can
|
|
66
|
+
* parse, and a form reset with nothing declared are all the same situation,
|
|
67
|
+
* and all three draw `min` filled: at the default of zero that is the empty
|
|
68
|
+
* row this always drew, and at `min="2"` it is two marks, the way an empty
|
|
69
|
+
* `Slider` sits at its own minimum rather than at zero. The value is still
|
|
70
|
+
* `""` through all of it, so `required` still refuses it and `valueAsNumber`
|
|
71
|
+
* is still `NaN`: what the row shows is the bottom of the scale, not an
|
|
72
|
+
* answer somebody gave.
|
|
73
|
+
*
|
|
74
|
+
* *The steps* are the scores from `step` up to `max` that are not below it,
|
|
75
|
+
* so `min="2"` on five marks offers 2 to 5 and leaves the first two marks
|
|
76
|
+
* filled and unpressable. At zero nothing is dropped, which is why the
|
|
77
|
+
* default row is unchanged.
|
|
78
|
+
*
|
|
79
|
+
* *The keyboard* moves through those steps and no others. `wrap` is already
|
|
80
|
+
* false here, so arrowing down from the lowest one holds its ground rather
|
|
81
|
+
* than reaching a score this property forbids, and the tab stop an unrated
|
|
82
|
+
* rating carries sits on the lowest score offered rather than on the first
|
|
83
|
+
* mark.
|
|
84
|
+
*
|
|
85
|
+
* *A `min` between two steps* is a floor and not a step: `min="1.5"` with
|
|
86
|
+
* whole marks clamps the value at 1.5 and offers 2 upward, since 1.5 is not
|
|
87
|
+
* somewhere the row can be pressed. That is a real thing to ask for and is
|
|
88
|
+
* left to mean what it says.
|
|
89
|
+
*
|
|
90
|
+
* *A `min` outside the marks is refused rather than obeyed*, and says so
|
|
91
|
+
* once on the console. Below zero and anything that is not a number are read
|
|
92
|
+
* as zero, there being no mark to draw for a negative score; above `max` is
|
|
93
|
+
* read as `max`, which collapses the scale to its top score rather than
|
|
94
|
+
* leaving a rating with nothing to press. A property that silently accepts
|
|
95
|
+
* nonsense is the fault this pays off, so neither case is quietly ignored.
|
|
61
96
|
*/
|
|
62
97
|
min: number;
|
|
63
98
|
/**
|
|
@@ -120,9 +155,28 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
|
|
|
120
155
|
*/
|
|
121
156
|
private previewing;
|
|
122
157
|
protected readonly keys: KeyboardNavigationController;
|
|
158
|
+
/**
|
|
159
|
+
* The floor the row actually uses, which is `min` where `min` is one.
|
|
160
|
+
*
|
|
161
|
+
* Between zero and `max`, and zero for anything that is not a number, per
|
|
162
|
+
* the note on `min`. Derived on read rather than corrected into the property,
|
|
163
|
+
* so `rating.min` still reports what the consumer set and the warning below
|
|
164
|
+
* can tell the two apart.
|
|
165
|
+
*/
|
|
166
|
+
private get floor();
|
|
123
167
|
/** The score, as a number, clamped to what the marks can show. */
|
|
124
168
|
get score(): number;
|
|
125
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Every score the marks can show, which is `max / step` of them.
|
|
171
|
+
*
|
|
172
|
+
* The whole row rather than the part `min` leaves open, because this is what
|
|
173
|
+
* the stylesheet lays the targets out against: `--rating-steps` is the pitch
|
|
174
|
+
* and a step's own place in this list is where it sits along the row. Counted
|
|
175
|
+
* from the part a reader can press instead, a floor above zero would widen
|
|
176
|
+
* every target and slide the row left.
|
|
177
|
+
*/
|
|
178
|
+
private get positions();
|
|
179
|
+
/** Every score a reader can choose, which is the positions `min` leaves open. */
|
|
126
180
|
private get steps();
|
|
127
181
|
/**
|
|
128
182
|
* The options, as the shared controller wants them.
|
|
@@ -182,10 +236,12 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
|
|
|
182
236
|
* form holding an unrated required rating submits, `:valid` true and the
|
|
183
237
|
* entry present but empty. `reportRequired` is what makes the property real.
|
|
184
238
|
*
|
|
185
|
-
* **Empty is `value === ''` rather than a score
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
* it. What `required` asks is
|
|
239
|
+
* **Empty is `value === ''` rather than a score at the floor**, and the two
|
|
240
|
+
* are not the same thing: an unanswered row draws `min`, per the note on that
|
|
241
|
+
* property, so a control that read its own picture would call every rating
|
|
242
|
+
* with `min="2"` answered the moment it rendered. What `required` asks is
|
|
243
|
+
* whether the reader answered at all, which only the value knows. A consumer
|
|
244
|
+
* who writes `value="0"` has answered, and is not refused.
|
|
189
245
|
*
|
|
190
246
|
* `Slider` is deliberately not given the same treatment: a native
|
|
191
247
|
* `<input type="range" required>` is always valid, because a range always
|
|
@@ -197,6 +253,8 @@ export declare class HelixHeadlessRating extends HelixHeadlessRating_base {
|
|
|
197
253
|
private readonly handleLeave;
|
|
198
254
|
connectedCallback(): void;
|
|
199
255
|
disconnectedCallback(): void;
|
|
256
|
+
/** Whether the console has already been told about a `min` off the scale. */
|
|
257
|
+
private warned;
|
|
200
258
|
protected updated(changed: PropertyValues): void;
|
|
201
259
|
protected willUpdate(changed: PropertyValues): void;
|
|
202
260
|
protected firstUpdated(changed: PropertyValues): void;
|
|
@@ -89,18 +89,28 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
89
89
|
this.handleLeave = () => {
|
|
90
90
|
this.previewing = undefined;
|
|
91
91
|
};
|
|
92
|
+
this.warned = false;
|
|
93
|
+
}
|
|
94
|
+
get floor() {
|
|
95
|
+
if (!Number.isFinite(this.min)) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
return Math.min(Math.max(this.min, 0), Math.max(this.max, 0));
|
|
92
99
|
}
|
|
93
100
|
get score() {
|
|
94
101
|
const parsed = Number.parseFloat(this.value);
|
|
95
102
|
if (!Number.isFinite(parsed)) {
|
|
96
|
-
return
|
|
103
|
+
return this.floor;
|
|
97
104
|
}
|
|
98
|
-
return Math.min(Math.max(parsed,
|
|
105
|
+
return Math.min(Math.max(parsed, this.floor), this.max);
|
|
99
106
|
}
|
|
100
|
-
get
|
|
107
|
+
get positions() {
|
|
101
108
|
const count = Math.max(1, Math.round(this.max / this.step));
|
|
102
109
|
return Array.from({ length: count }, (_, index) => Number(((index + 1) * this.step).toFixed(2)));
|
|
103
110
|
}
|
|
111
|
+
get steps() {
|
|
112
|
+
return this.positions.filter((score) => score >= this.floor);
|
|
113
|
+
}
|
|
104
114
|
get options() {
|
|
105
115
|
const found = [...(this.renderRoot?.querySelectorAll('[part="option"]') ?? [])];
|
|
106
116
|
return found.map((element) => ({
|
|
@@ -157,6 +167,12 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
157
167
|
updated(changed) {
|
|
158
168
|
super.updated?.(changed);
|
|
159
169
|
this.sync();
|
|
170
|
+
if (this.floor !== this.min && !this.warned) {
|
|
171
|
+
this.warned = true;
|
|
172
|
+
console.warn(`<${this.localName}> has min="${this.min}", which is not a score between 0 and its max of ` +
|
|
173
|
+
`${this.max}, so the lowest score it offers is ${this.floor}. Put min inside the marks, ` +
|
|
174
|
+
'or move max.');
|
|
175
|
+
}
|
|
160
176
|
}
|
|
161
177
|
willUpdate(changed) {
|
|
162
178
|
super.willUpdate(changed);
|
|
@@ -177,12 +193,16 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
177
193
|
const first = this.steps[0];
|
|
178
194
|
const mark = (layer, index) => html `<span part="mark">${this.markFor(layer, index)}</span>`;
|
|
179
195
|
const marksIn = (layer) => Array.from({ length: count }, (_, index) => mark(layer, index));
|
|
180
|
-
return html
|
|
196
|
+
return html `${this.renderGroupLabel()}
|
|
197
|
+
<div part="field">
|
|
198
|
+
<div
|
|
181
199
|
part="marks"
|
|
182
200
|
role=${this.readOnly ? 'img' : 'radiogroup'}
|
|
183
201
|
aria-label=${this.readOnly ? said : this.label || nothing}
|
|
202
|
+
aria-describedby=${this.describedBy ?? nothing}
|
|
203
|
+
aria-required=${this.required && !this.readOnly ? 'true' : nothing}
|
|
184
204
|
data-slotted=${this.markSource ? '' : nothing}
|
|
185
|
-
style=${`--rating-full: ${Math.floor(showing)}; --rating-part: ${showing - Math.floor(showing)}; --rating-steps: ${this.
|
|
205
|
+
style=${`--rating-full: ${Math.floor(showing)}; --rating-part: ${showing - Math.floor(showing)}; --rating-steps: ${this.positions.length}`}
|
|
186
206
|
@pointercancel=${this.handleDragCancel}
|
|
187
207
|
@pointerdown=${this.handleDragStart}
|
|
188
208
|
@pointermove=${this.handleDragMove}
|
|
@@ -207,7 +227,10 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
207
227
|
>
|
|
208
228
|
${this.readOnly
|
|
209
229
|
? nothing
|
|
210
|
-
:
|
|
230
|
+
:
|
|
231
|
+
this.positions.map((score, index) => score < this.floor
|
|
232
|
+
? nothing
|
|
233
|
+
: html `<button
|
|
211
234
|
part="option"
|
|
212
235
|
role="radio"
|
|
213
236
|
type="button"
|
|
@@ -216,7 +239,9 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
216
239
|
data-score=${String(score)}
|
|
217
240
|
style=${`--rating-step: ${index + 1}`}
|
|
218
241
|
?disabled=${this.isDisabled}
|
|
219
|
-
tabindex=${this.score === score || (this.score ===
|
|
242
|
+
tabindex=${this.score === score || (this.score === this.floor && score === first)
|
|
243
|
+
? '0'
|
|
244
|
+
: '-1'}
|
|
220
245
|
@click=${this.handleOptionClick}
|
|
221
246
|
@pointerenter=${this.handleOptionEnter}
|
|
222
247
|
></button>`)}
|
|
@@ -227,7 +252,9 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
|
|
|
227
252
|
aria-hidden="true"
|
|
228
253
|
>${showing}</span
|
|
229
254
|
>`
|
|
230
|
-
: nothing}
|
|
255
|
+
: nothing}
|
|
256
|
+
</div>
|
|
257
|
+
${this.renderHint()}${this.renderFeedback()}`;
|
|
231
258
|
}
|
|
232
259
|
get valueAsNumber() {
|
|
233
260
|
return asNumber(this.value);
|
|
@@ -9,7 +9,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
9
9
|
* server's markup for a crawler regardless of which tab is open.
|
|
10
10
|
*
|
|
11
11
|
* @since 0.1.0
|
|
12
|
-
* @status
|
|
12
|
+
* @status stable
|
|
13
13
|
* @aliases TabPanel
|
|
14
14
|
* @documentation https://fusion.dev/helix/components/tabs
|
|
15
15
|
*
|
|
@@ -16,7 +16,7 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
16
16
|
* showing a panel is cheap; a set where it is not should use `manual`.
|
|
17
17
|
*
|
|
18
18
|
* @since 0.1.0
|
|
19
|
-
* @status
|
|
19
|
+
* @status stable
|
|
20
20
|
* @category navigation
|
|
21
21
|
* @aliases TabList, Segmented control
|
|
22
22
|
* @documentation https://fusion.dev/helix/components/tabs
|
package/dist/tabs/tab/tab.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
12
12
|
* `aria-controls` pointing at nothing is worse than not being there.
|
|
13
13
|
*
|
|
14
14
|
* @since 0.1.0
|
|
15
|
-
* @status
|
|
15
|
+
* @status stable
|
|
16
16
|
* @aliases Tab
|
|
17
17
|
* @documentation https://fusion.dev/helix/components/tabs
|
|
18
18
|
*
|
|
@@ -43,6 +43,19 @@ declare const HelixHeadlessTimePicker_base: (new (...args: any[]) => import("@fu
|
|
|
43
43
|
* string, which is the whole reason the shape is that one. What each row *says*
|
|
44
44
|
* is the reader's own convention, from `Intl`.
|
|
45
45
|
*
|
|
46
|
+
* **The chosen row is marked in the markup, not worked out on upgrade.** The
|
|
47
|
+
* listbox is handed the answer as `.value`, and lit-ssr serializes no property
|
|
48
|
+
* binding, so a served `value="09:30"` shipped ninety-six rows with nothing on
|
|
49
|
+
* any of them saying which one it was: the mark arrived when the listbox
|
|
50
|
+
* upgraded and ran `place`. So each option is given `selected` as well, which
|
|
51
|
+
* is an attribute binding and does serve, and which the listbox then agrees
|
|
52
|
+
* with rather than fights, `place` writing the same answer from the same value.
|
|
53
|
+
* `selected` is the declaration a consumer writes on `Option` anywhere else in
|
|
54
|
+
* the library, and Date Picker expresses its own choice as markup for the same
|
|
55
|
+
* reason: a picker whose answer is invisible until a script runs is a picker
|
|
56
|
+
* whose answer is not served at all. Both readings do it, the list and the
|
|
57
|
+
* wheels, a wheel being a listbox like the list is.
|
|
58
|
+
*
|
|
46
59
|
* @since 0.1.0
|
|
47
60
|
* @status experimental
|
|
48
61
|
* @category forms
|
|
@@ -248,6 +248,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
248
248
|
label: option-label, text: option-text"
|
|
249
249
|
value=${one}
|
|
250
250
|
?disabled=${barred(one)}
|
|
251
|
+
?selected=${one === value}
|
|
251
252
|
>${written(one)}</${this.tag('option')}
|
|
252
253
|
>`)}
|
|
253
254
|
</${this.tag('listbox')}>`;
|
|
@@ -291,6 +292,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
|
|
|
291
292
|
label: option-label, text: option-text"
|
|
292
293
|
value=${at}
|
|
293
294
|
?disabled=${this.barred(at)}
|
|
295
|
+
?selected=${this.held.includes(at)}
|
|
294
296
|
data-between=${between ? '' : nothing}
|
|
295
297
|
>${writeTime(at, this.locale)}</${this.tag('option')}
|
|
296
298
|
>`;
|
|
@@ -20,7 +20,7 @@ import type { HelixHeadlessTourStop } from '../stop/tour-stop.js';
|
|
|
20
20
|
* nothing needs to be: a tour starts when a reader asks for it.
|
|
21
21
|
*
|
|
22
22
|
* @since 0.1.0
|
|
23
|
-
* @status
|
|
23
|
+
* @status stable
|
|
24
24
|
* @category overlays
|
|
25
25
|
* @aliases Coachmark, Product tour, Walkthrough, Onboarding
|
|
26
26
|
* @documentation https://fusion.dev/helix/components/tour
|
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.49.0",
|
|
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.49.0",
|
|
50
|
+
"@fusion.dev/helix-icons": "0.49.0",
|
|
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.49.0",
|
|
56
|
+
"@fusion.dev/helix-testing": "0.49.0",
|
|
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",
|