@fusion.dev/helix-headless 0.53.0 → 0.53.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 +23 -3
- package/dist/checkbox/root/checkbox-root.js +1 -1
- package/dist/combobox/root/combobox-root.d.ts +16 -0
- package/dist/combobox/root/combobox-root.js +4 -1
- package/dist/data-table/root/data-table-root.d.ts +23 -0
- package/dist/data-table/root/data-table-root.js +8 -3
- package/dist/radio/root/radio-root.d.ts +11 -1
- package/dist/radio/root/radio-root.js +5 -1
- package/dist/select/root/select-root.d.ts +24 -0
- package/dist/select/root/select-root.js +4 -1
- package/dist/switch/root/switch-root.js +1 -1
- package/dist/tabs/root/tabs-root.js +11 -4
- package/dist/tabs/tab/tab.d.ts +11 -1
- package/dist/tabs/tab/tab.js +5 -1
- package/dist/toggle/root/toggle-root.d.ts +11 -1
- package/dist/toggle/root/toggle-root.js +5 -1
- package/dist/tree/item/tree-item.js +4 -3
- package/dist/tree/root/tree-root.js +8 -4
- package/package.json +5 -5
package/custom-elements.json
CHANGED
|
@@ -20811,6 +20811,16 @@
|
|
|
20811
20811
|
"description": "The chosen options themselves, for their words.\n\nRead off the options rather than stored, because the words are the\nconsumer's content and a copy of them goes stale the moment they change it.\nSelect records the same.\n\nIn the value's own order, which is the order they were chosen. It has to\nbe: a created answer is not in the list at all, so there is no position in\nthe list to give it. Neither order ever reshuffles what is already there,\nso a tag does not move out from under the pointer about to remove it.",
|
|
20812
20812
|
"readonly": true
|
|
20813
20813
|
},
|
|
20814
|
+
{
|
|
20815
|
+
"kind": "field",
|
|
20816
|
+
"name": "unresolved",
|
|
20817
|
+
"type": {
|
|
20818
|
+
"text": "boolean"
|
|
20819
|
+
},
|
|
20820
|
+
"privacy": "private",
|
|
20821
|
+
"description": "Whether a value is declared and this render cannot say what it is.\n\n**A placeholder is a statement, so a render that cannot read the words does\nnot make it.** `value` is an attribute and a server has it; the words are\nthe options' and the options are light-DOM children a server has none of.\nThe field then came out empty with \"Search owners\" over it, which says\nnothing is chosen where the consumer had already said what is. Blank, it\nsays nothing, keeps its box, and is filled in by the render after\nhydration, which is the answer `Select.Root.display` reaches for in the same\nposition and for the same reason.\n\nThe placeholder still stands where there is genuinely no value, which is\nwhat it is for and the one case a server can be sure of.",
|
|
20822
|
+
"readonly": true
|
|
20823
|
+
},
|
|
20814
20824
|
{
|
|
20815
20825
|
"kind": "field",
|
|
20816
20826
|
"name": "showing",
|
|
@@ -25430,12 +25440,22 @@
|
|
|
25430
25440
|
},
|
|
25431
25441
|
"privacy": "protected",
|
|
25432
25442
|
"readonly": true,
|
|
25433
|
-
"description": "Emptiness is a fact about the data here, not about slotted children.\n\n**The page rather than the window**, which is the difference between the two\ncounts `view` carries and the reason this reads `available`. A windowed\ntable draws a slice, and a slice can be short of the rows that are there:\nasking whether *this* slice has rows says \"nothing matched\" about a result\nthat matched plenty.\n\nThe mixin's own answer counts children with no `slot`, which for this\nelement is the columns. Left in place, a table with columns and no data\nwould call itself full.",
|
|
25443
|
+
"description": "Emptiness is a fact about the data here, not about slotted children.\n\n**The page rather than the window**, which is the difference between the two\ncounts `view` carries and the reason this reads `available`. A windowed\ntable draws a slice, and a slice can be short of the rows that are there:\nasking whether *this* slice has rows says \"nothing matched\" about a result\nthat matched plenty.\n\nThe mixin's own answer counts children with no `slot`, which for this\nelement is the columns. Left in place, a table with columns and no data\nwould call itself full.\n\n**`sourceKnown` is the half the mixin was keeping**, and overriding without\nit is what put \"Nothing to show\" over eight rows of files on every server\nrender of this family's own preview. `MixinCollection.hasRows` answers true\nuntil the rows have been counted for exactly that reason, and a count is\nwhat this replaced.",
|
|
25434
25444
|
"inheritedFrom": {
|
|
25435
25445
|
"name": "MixinCollection",
|
|
25436
25446
|
"module": "src/mixins/mixin-collection/mixin-collection.ts"
|
|
25437
25447
|
}
|
|
25438
25448
|
},
|
|
25449
|
+
{
|
|
25450
|
+
"kind": "field",
|
|
25451
|
+
"name": "sourceKnown",
|
|
25452
|
+
"type": {
|
|
25453
|
+
"text": "boolean"
|
|
25454
|
+
},
|
|
25455
|
+
"privacy": "private",
|
|
25456
|
+
"description": "Whether this render can tell a result with no rows from one not yet handed\nover.\n\n**Unknown is not empty, which is what an empty state must never say.** The\nrows are a property, and a property is set by whoever holds the element: a\nconsumer binding it in a template has it on the server, and a consumer\nassigning it from a script has it only once that script has run, which is\nafter the parse a server's markup ends at. A render that cannot see a\ndocument cannot tell the two apart, and a hydrating render must not look,\nits output having to be the server's.\n\nThe same shape as `Heatmap.sourceKnown`, asked of a property rather than of\na document, and `childrenKnown` is already both halves of the question: a\nserver has no tree and a hydrating render may not read one.",
|
|
25457
|
+
"readonly": true
|
|
25458
|
+
},
|
|
25439
25459
|
{
|
|
25440
25460
|
"kind": "method",
|
|
25441
25461
|
"name": "requestSort",
|
|
@@ -75607,10 +75627,10 @@
|
|
|
75607
75627
|
"kind": "field",
|
|
75608
75628
|
"name": "display",
|
|
75609
75629
|
"type": {
|
|
75610
|
-
"text": "string"
|
|
75630
|
+
"text": "string | typeof nothing"
|
|
75611
75631
|
},
|
|
75612
75632
|
"privacy": "private",
|
|
75613
|
-
"description": "What the trigger says: the chosen options' words, or the placeholder.\n\nRead off the options rather than stored, because the words are the\nconsumer's content and storing a copy is storing something that goes stale\nthe moment they change it.\n\n**Off this element's own children, not the listbox's**, which reach the\nlistbox through a slot and so only after a render has made it: read there,\nthe first render showed the placeholder over a chosen value and waited on a\n`slotchange`, which a declarative shadow root fires before anything listens,\nso a hydrated select kept \"Choose\" for good. Nothing on a server or while\nhydrating what one sent, which drew the placeholder too.",
|
|
75633
|
+
"description": "What the trigger says: the chosen options' words, or the placeholder.\n\nRead off the options rather than stored, because the words are the\nconsumer's content and storing a copy is storing something that goes stale\nthe moment they change it.\n\n**Off this element's own children, not the listbox's**, which reach the\nlistbox through a slot and so only after a render has made it: read there,\nthe first render showed the placeholder over a chosen value and waited on a\n`slotchange`, which a declarative shadow root fires before anything listens,\nso a hydrated select kept \"Choose\" for good. Nothing on a server or while\nhydrating what one sent, which drew the placeholder too.\n\n**And the placeholder is a statement, so a render that cannot read the\nwords does not make it.** \"Choose\" means nothing is chosen. `value` is an\nattribute and a server has it, the words are the children's and a server\nhas none, so a `<helix-select default-value=\"two\">` served \"Choose\" over an\nanswer the consumer had already given: false to a reader, and false to a\ncrawler, on every select in the library's own examples. Where a value is\ndeclared and the word for it cannot be read, the trigger says nothing and\nkeeps its box, which is the answer this library reaches for everywhere it\ncannot tell empty from unknown. `firstUpdated` asks for the render that\nfills it in.\n\nThe placeholder still stands where there is genuinely no value, which is\nwhat it is for and the one case a server can be sure of.\n\n**`nothing` and never `''`, which is the difference between a word that\narrives and one that never does.** Lit's hydration primes a child part with\nwhatever the render computed, and `''` is a primitive, so `_commitText`\ntakes the branch that assumes the server left a text node to overwrite.\nThere is none, the write lands on the closing part marker's comment data,\nand the trigger stays empty for the life of the page: measured by\n`check:spec:dsd`, which caught all nine of these and showed a blank select\nbeside a client's \"Option Two\". `nothing` primes the sentinel instead, so\nthe commit creates the text node it needs.",
|
|
75614
75634
|
"readonly": true
|
|
75615
75635
|
},
|
|
75616
75636
|
{
|
|
@@ -61,7 +61,7 @@ let HelixHeadlessCheckbox = class HelixHeadlessCheckbox extends MixinControllerC
|
|
|
61
61
|
<input
|
|
62
62
|
part="control"
|
|
63
63
|
type="checkbox"
|
|
64
|
-
.checked=${this.checked}
|
|
64
|
+
.checked=${this.checked || nothing}
|
|
65
65
|
.indeterminate=${this.indeterminate}
|
|
66
66
|
?disabled=${this.isDisabled}
|
|
67
67
|
?required=${this.required}
|
|
@@ -208,6 +208,22 @@ export declare class HelixHeadlessCombobox extends HelixHeadlessCombobox_base {
|
|
|
208
208
|
* so a tag does not move out from under the pointer about to remove it.
|
|
209
209
|
*/
|
|
210
210
|
private get chosen();
|
|
211
|
+
/**
|
|
212
|
+
* Whether a value is declared and this render cannot say what it is.
|
|
213
|
+
*
|
|
214
|
+
* **A placeholder is a statement, so a render that cannot read the words does
|
|
215
|
+
* not make it.** `value` is an attribute and a server has it; the words are
|
|
216
|
+
* the options' and the options are light-DOM children a server has none of.
|
|
217
|
+
* The field then came out empty with "Search owners" over it, which says
|
|
218
|
+
* nothing is chosen where the consumer had already said what is. Blank, it
|
|
219
|
+
* says nothing, keeps its box, and is filled in by the render after
|
|
220
|
+
* hydration, which is the answer `Select.Root.display` reaches for in the same
|
|
221
|
+
* position and for the same reason.
|
|
222
|
+
*
|
|
223
|
+
* The placeholder still stands where there is genuinely no value, which is
|
|
224
|
+
* what it is for and the one case a server can be sure of.
|
|
225
|
+
*/
|
|
226
|
+
private get unresolved();
|
|
211
227
|
/**
|
|
212
228
|
* Whether the filter has left anything to choose.
|
|
213
229
|
*
|
|
@@ -147,6 +147,9 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
147
147
|
.filter((value) => known || words.has(value))
|
|
148
148
|
.map((value) => ({ text: words.get(value) ?? value, value }));
|
|
149
149
|
}
|
|
150
|
+
get unresolved() {
|
|
151
|
+
return this.values.length > 0 && !this.childrenKnown;
|
|
152
|
+
}
|
|
150
153
|
get showing() {
|
|
151
154
|
return (this.listbox?.options ?? []).some((option) => !option.hidden);
|
|
152
155
|
}
|
|
@@ -304,7 +307,7 @@ let HelixHeadlessCombobox = class HelixHeadlessCombobox extends MixinControllerO
|
|
|
304
307
|
aria-autocomplete=${this.suggest ? 'both' : 'list'}
|
|
305
308
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
306
309
|
aria-describedby=${this.describedBy ?? nothing}
|
|
307
|
-
placeholder=${(this.chosen.length > 0 ? '' : this.placeholder) || nothing}
|
|
310
|
+
placeholder=${(this.chosen.length > 0 || this.unresolved ? '' : this.placeholder) || nothing}
|
|
308
311
|
?disabled=${this.isDisabled}
|
|
309
312
|
.value=${this.multiple || this.open ? this.suggestion || this.typed : this.word}
|
|
310
313
|
@input=${this.handleInput}
|
|
@@ -473,8 +473,31 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
|
|
|
473
473
|
* The mixin's own answer counts children with no `slot`, which for this
|
|
474
474
|
* element is the columns. Left in place, a table with columns and no data
|
|
475
475
|
* would call itself full.
|
|
476
|
+
*
|
|
477
|
+
* **`sourceKnown` is the half the mixin was keeping**, and overriding without
|
|
478
|
+
* it is what put "Nothing to show" over eight rows of files on every server
|
|
479
|
+
* render of this family's own preview. `MixinCollection.hasRows` answers true
|
|
480
|
+
* until the rows have been counted for exactly that reason, and a count is
|
|
481
|
+
* what this replaced.
|
|
476
482
|
*/
|
|
477
483
|
protected get hasRows(): boolean;
|
|
484
|
+
/**
|
|
485
|
+
* Whether this render can tell a result with no rows from one not yet handed
|
|
486
|
+
* over.
|
|
487
|
+
*
|
|
488
|
+
* **Unknown is not empty, which is what an empty state must never say.** The
|
|
489
|
+
* rows are a property, and a property is set by whoever holds the element: a
|
|
490
|
+
* consumer binding it in a template has it on the server, and a consumer
|
|
491
|
+
* assigning it from a script has it only once that script has run, which is
|
|
492
|
+
* after the parse a server's markup ends at. A render that cannot see a
|
|
493
|
+
* document cannot tell the two apart, and a hydrating render must not look,
|
|
494
|
+
* its output having to be the server's.
|
|
495
|
+
*
|
|
496
|
+
* The same shape as `Heatmap.sourceKnown`, asked of a property rather than of
|
|
497
|
+
* a document, and `childrenKnown` is already both halves of the question: a
|
|
498
|
+
* server has no tree and a hydrating render may not read one.
|
|
499
|
+
*/
|
|
500
|
+
private get sourceKnown();
|
|
478
501
|
/**
|
|
479
502
|
* Asks for a different order, and reports whether the request stood.
|
|
480
503
|
*
|
|
@@ -353,7 +353,10 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
353
353
|
})).length;
|
|
354
354
|
}
|
|
355
355
|
get hasRows() {
|
|
356
|
-
return this.view.available > 0;
|
|
356
|
+
return this.view.available > 0 || !this.sourceKnown;
|
|
357
|
+
}
|
|
358
|
+
get sourceKnown() {
|
|
359
|
+
return this.childrenKnown;
|
|
357
360
|
}
|
|
358
361
|
requestSort(next) {
|
|
359
362
|
return requestChange({
|
|
@@ -618,12 +621,14 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
618
621
|
const template = this.template;
|
|
619
622
|
const columns = this.shownColumns;
|
|
620
623
|
const pagination = this.tag('pagination');
|
|
624
|
+
const counted = this.mode === 'server' ? Math.max(this.total, available) : matched;
|
|
625
|
+
const countKnown = counted > 0 || this.sourceKnown;
|
|
621
626
|
return html `<div part="toolbar"><slot name="toolbar"></slot></div>
|
|
622
627
|
<div
|
|
623
628
|
part="table"
|
|
624
629
|
role="rowgroup"
|
|
625
630
|
aria-busy=${this.loading ? 'true' : nothing}
|
|
626
|
-
aria-rowcount=${
|
|
631
|
+
aria-rowcount=${countKnown ? counted + 1 : nothing}
|
|
627
632
|
aria-colcount=${columns.length ? columns.length + (this.selectable ? 1 : 0) : nothing}
|
|
628
633
|
style=${template ? `--data-table-template: ${template}` : nothing}
|
|
629
634
|
@scroll=${this.handleScroll}
|
|
@@ -648,7 +653,7 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
|
|
|
648
653
|
status: pagination-status"
|
|
649
654
|
page=${this.page}
|
|
650
655
|
page-size=${this.pageSize || 1}
|
|
651
|
-
total=${matched}
|
|
656
|
+
total=${countKnown ? matched : nothing}
|
|
652
657
|
@helix-navigate=${this.handleNavigate}
|
|
653
658
|
></${pagination}>`
|
|
654
659
|
: nothing}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessRadio_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
5
5
|
* @summary One choice in a radio group.
|
|
@@ -89,6 +89,16 @@ export declare class HelixHeadlessRadio extends HelixHeadlessRadio_base {
|
|
|
89
89
|
* same reason.
|
|
90
90
|
*/
|
|
91
91
|
focusable: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Asks for the render that follows a hydration, so the stop stops being every
|
|
94
|
+
* choice's.
|
|
95
|
+
*
|
|
96
|
+
* `tabindex` reads `childrenKnown` and a hydrating render is one where that is
|
|
97
|
+
* false, which is what keeps the hydrating render the server's. Nothing else
|
|
98
|
+
* would ask: the group writes an already-false `focusable` onto every choice
|
|
99
|
+
* but one, which changes nothing and so re-renders nothing.
|
|
100
|
+
*/
|
|
101
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
92
102
|
protected render(): TemplateResult;
|
|
93
103
|
}
|
|
94
104
|
declare global {
|
|
@@ -18,6 +18,10 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends MixinSelectableItem(He
|
|
|
18
18
|
set defaultSelected(next) {
|
|
19
19
|
this.toggleAttribute('selected', next);
|
|
20
20
|
}
|
|
21
|
+
firstUpdated(changed) {
|
|
22
|
+
super.firstUpdated(changed);
|
|
23
|
+
this.finishHydration();
|
|
24
|
+
}
|
|
21
25
|
render() {
|
|
22
26
|
return html `<button
|
|
23
27
|
part="item"
|
|
@@ -25,7 +29,7 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends MixinSelectableItem(He
|
|
|
25
29
|
role="radio"
|
|
26
30
|
aria-checked=${this.selected ? 'true' : 'false'}
|
|
27
31
|
?disabled=${this.disabled}
|
|
28
|
-
tabindex=${this.selected || this.focusable ? '0' : '-1'}
|
|
32
|
+
tabindex=${this.selected || this.focusable || !this.childrenKnown ? '0' : '-1'}
|
|
29
33
|
@click=${() => this.select()}
|
|
30
34
|
>
|
|
31
35
|
<span part="box"><span part="mark" aria-hidden="true"></span></span>
|
|
@@ -134,6 +134,30 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
|
|
|
134
134
|
* `slotchange`, which a declarative shadow root fires before anything listens,
|
|
135
135
|
* so a hydrated select kept "Choose" for good. Nothing on a server or while
|
|
136
136
|
* hydrating what one sent, which drew the placeholder too.
|
|
137
|
+
*
|
|
138
|
+
* **And the placeholder is a statement, so a render that cannot read the
|
|
139
|
+
* words does not make it.** "Choose" means nothing is chosen. `value` is an
|
|
140
|
+
* attribute and a server has it, the words are the children's and a server
|
|
141
|
+
* has none, so a `<helix-select default-value="two">` served "Choose" over an
|
|
142
|
+
* answer the consumer had already given: false to a reader, and false to a
|
|
143
|
+
* crawler, on every select in the library's own examples. Where a value is
|
|
144
|
+
* declared and the word for it cannot be read, the trigger says nothing and
|
|
145
|
+
* keeps its box, which is the answer this library reaches for everywhere it
|
|
146
|
+
* cannot tell empty from unknown. `firstUpdated` asks for the render that
|
|
147
|
+
* fills it in.
|
|
148
|
+
*
|
|
149
|
+
* The placeholder still stands where there is genuinely no value, which is
|
|
150
|
+
* what it is for and the one case a server can be sure of.
|
|
151
|
+
*
|
|
152
|
+
* **`nothing` and never `''`, which is the difference between a word that
|
|
153
|
+
* arrives and one that never does.** Lit's hydration primes a child part with
|
|
154
|
+
* whatever the render computed, and `''` is a primitive, so `_commitText`
|
|
155
|
+
* takes the branch that assumes the server left a text node to overwrite.
|
|
156
|
+
* There is none, the write lands on the closing part marker's comment data,
|
|
157
|
+
* and the trigger stays empty for the life of the page: measured by
|
|
158
|
+
* `check:spec:dsd`, which caught all nine of these and showed a blank select
|
|
159
|
+
* beside a client's "Option Two". `nothing` primes the sentinel instead, so
|
|
160
|
+
* the commit creates the text node it needs.
|
|
137
161
|
*/
|
|
138
162
|
private get display();
|
|
139
163
|
/** Closes the list and puts focus back where it came from. */
|
|
@@ -74,7 +74,10 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
|
|
|
74
74
|
const words = optionsAmong(this.knownChildren)
|
|
75
75
|
.filter((option) => chosen.has(option.value))
|
|
76
76
|
.map((option) => option.text);
|
|
77
|
-
|
|
77
|
+
if (words.length > 0) {
|
|
78
|
+
return words.join(', ');
|
|
79
|
+
}
|
|
80
|
+
return chosen.size > 0 && !this.childrenKnown ? nothing : this.placeholder;
|
|
78
81
|
}
|
|
79
82
|
close() {
|
|
80
83
|
if (!this.open) {
|
|
@@ -47,7 +47,7 @@ let HelixHeadlessSwitch = class HelixHeadlessSwitch extends MixinControllerCheck
|
|
|
47
47
|
part="control"
|
|
48
48
|
type="checkbox"
|
|
49
49
|
role="switch"
|
|
50
|
-
.checked=${this.checked}
|
|
50
|
+
.checked=${this.checked || nothing}
|
|
51
51
|
?disabled=${this.isDisabled}
|
|
52
52
|
?required=${this.required}
|
|
53
53
|
aria-describedby=${this.describedBy ?? nothing}
|
|
@@ -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 { customElement, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, whenHydrated, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { HelixHeadlessTabPanel } from '../panel/tab-panel.js';
|
|
@@ -47,12 +47,19 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
47
47
|
const declared = this.active === '' ? tabs.find((tab) => tab.selected) : undefined;
|
|
48
48
|
const value = (declared ?? this.resolveActive(tabs))?.value ?? '';
|
|
49
49
|
for (const tab of tabs) {
|
|
50
|
-
|
|
50
|
+
const selected = tab.value === value;
|
|
51
|
+
whenHydrated(tab, () => {
|
|
52
|
+
tab.selected = selected;
|
|
53
|
+
});
|
|
51
54
|
}
|
|
52
55
|
const names = new Map(tabs.map((tab) => [tab.value, (tab.textContent ?? '').trim()]));
|
|
53
56
|
for (const panel of this.panels) {
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const selected = panel.value === value;
|
|
58
|
+
const label = names.get(panel.value) ?? '';
|
|
59
|
+
whenHydrated(panel, () => {
|
|
60
|
+
panel.selected = selected;
|
|
61
|
+
panel.label = label;
|
|
62
|
+
});
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
connectedCallback() {
|
package/dist/tabs/tab/tab.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
5
5
|
* @summary One tab in a tab list.
|
|
@@ -27,6 +27,16 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
|
|
|
27
27
|
*/
|
|
28
28
|
export declare class HelixHeadlessTab extends HelixHeadlessTab_base {
|
|
29
29
|
protected itemPart: string;
|
|
30
|
+
/**
|
|
31
|
+
* Asks for the render that follows a hydration, so the stop stops being every
|
|
32
|
+
* tab's.
|
|
33
|
+
*
|
|
34
|
+
* `tabindex` reads `childrenKnown` and a hydrating render is one where that is
|
|
35
|
+
* false, which is what keeps the hydrating render the server's. Nothing else
|
|
36
|
+
* would ask: the root writes an already-false `selected` onto an unchosen tab,
|
|
37
|
+
* which changes nothing and so re-renders nothing.
|
|
38
|
+
*/
|
|
39
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
30
40
|
protected render(): TemplateResult;
|
|
31
41
|
}
|
|
32
42
|
declare global {
|
package/dist/tabs/tab/tab.js
CHANGED
|
@@ -11,6 +11,10 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixE
|
|
|
11
11
|
super(...arguments);
|
|
12
12
|
this.itemPart = 'tab';
|
|
13
13
|
}
|
|
14
|
+
firstUpdated(changed) {
|
|
15
|
+
super.firstUpdated(changed);
|
|
16
|
+
this.finishHydration();
|
|
17
|
+
}
|
|
14
18
|
render() {
|
|
15
19
|
return html `<button
|
|
16
20
|
part="tab"
|
|
@@ -18,7 +22,7 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixE
|
|
|
18
22
|
type="button"
|
|
19
23
|
aria-selected=${this.selected ? 'true' : 'false'}
|
|
20
24
|
?disabled=${this.disabled}
|
|
21
|
-
tabindex=${this.selected ? '0' : '-1'}
|
|
25
|
+
tabindex=${this.selected || !this.childrenKnown ? '0' : '-1'}
|
|
22
26
|
@click=${() => this.select()}
|
|
23
27
|
>
|
|
24
28
|
<slot></slot>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
5
5
|
* @summary One option in a toggle group.
|
|
@@ -65,6 +65,16 @@ export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
|
|
|
65
65
|
* hydration reports and does not patch up.
|
|
66
66
|
*/
|
|
67
67
|
focusable: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Asks for the render that follows a hydration, so the stop stops being every
|
|
70
|
+
* choice's.
|
|
71
|
+
*
|
|
72
|
+
* `tabindex` reads `childrenKnown` and a hydrating render is one where that is
|
|
73
|
+
* false, which is what keeps the hydrating render the server's. Nothing else
|
|
74
|
+
* would ask: the group writes an already-false `focusable` onto every choice
|
|
75
|
+
* but one, which changes nothing and so re-renders nothing.
|
|
76
|
+
*/
|
|
77
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
68
78
|
protected render(): TemplateResult;
|
|
69
79
|
}
|
|
70
80
|
declare global {
|
|
@@ -18,6 +18,10 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
18
18
|
set defaultSelected(next) {
|
|
19
19
|
this.toggleAttribute('selected', next);
|
|
20
20
|
}
|
|
21
|
+
firstUpdated(changed) {
|
|
22
|
+
super.firstUpdated(changed);
|
|
23
|
+
this.finishHydration();
|
|
24
|
+
}
|
|
21
25
|
render() {
|
|
22
26
|
return html `<button
|
|
23
27
|
part="item"
|
|
@@ -25,7 +29,7 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
25
29
|
role="radio"
|
|
26
30
|
aria-checked=${this.selected ? 'true' : 'false'}
|
|
27
31
|
?disabled=${this.disabled}
|
|
28
|
-
tabindex=${this.selected || this.focusable ? '0' : '-1'}
|
|
32
|
+
tabindex=${this.selected || this.focusable || !this.childrenKnown ? '0' : '-1'}
|
|
29
33
|
@click=${() => this.select()}
|
|
30
34
|
>
|
|
31
35
|
<slot></slot>
|
|
@@ -104,14 +104,15 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
|
|
|
104
104
|
render() {
|
|
105
105
|
const linked = this.href !== '';
|
|
106
106
|
const level = this.level ?? 1;
|
|
107
|
+
const depth = this.level ?? nothing;
|
|
107
108
|
const expanded = this.branch ? (this.open ? 'true' : 'false') : nothing;
|
|
108
|
-
const tabIndex = this.focusable ? '0' : '-1';
|
|
109
|
+
const tabIndex = this.focusable || !this.childrenKnown ? '0' : '-1';
|
|
109
110
|
return html `<div
|
|
110
111
|
part="item"
|
|
111
112
|
role=${linked ? 'none' : 'treeitem'}
|
|
112
113
|
aria-expanded=${linked ? nothing : expanded}
|
|
113
114
|
aria-selected=${linked ? nothing : this.selected ? 'true' : 'false'}
|
|
114
|
-
aria-level=${linked ? nothing :
|
|
115
|
+
aria-level=${linked ? nothing : depth}
|
|
115
116
|
tabindex=${linked ? nothing : tabIndex}
|
|
116
117
|
>
|
|
117
118
|
${linked
|
|
@@ -123,7 +124,7 @@ let HelixHeadlessTreeItem = class HelixHeadlessTreeItem extends MixinControllerO
|
|
|
123
124
|
rel=${this.rel || nothing}
|
|
124
125
|
aria-expanded=${expanded}
|
|
125
126
|
aria-selected=${this.selected ? 'true' : 'false'}
|
|
126
|
-
aria-level=${
|
|
127
|
+
aria-level=${depth}
|
|
127
128
|
tabindex=${tabIndex}
|
|
128
129
|
style="--tree-level: ${level}"
|
|
129
130
|
@click=${this.handleRowClick}
|
|
@@ -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 { closestInstance, customElement, HelixElement, inlineStep, MixinCollection, MixinControllerActive, scrollItemIntoView, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { closestInstance, customElement, HelixElement, inlineStep, MixinCollection, MixinControllerActive, scrollItemIntoView, whenHydrated, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { HelixHeadlessTreeItem } from '../item/tree-item.js';
|
|
@@ -101,8 +101,8 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
|
|
|
101
101
|
const held = visible.find((item) => item.focusable);
|
|
102
102
|
const stop = held ?? chosen;
|
|
103
103
|
for (const item of this.items) {
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
const selected = item.value !== '' && item.value === this.active;
|
|
105
|
+
const focusable = item === stop;
|
|
106
106
|
let level = 1;
|
|
107
107
|
let parent = item.parentElement;
|
|
108
108
|
while (parent && parent !== this) {
|
|
@@ -111,7 +111,11 @@ let HelixHeadlessTree = class HelixHeadlessTree extends MixinCollection(MixinCon
|
|
|
111
111
|
}
|
|
112
112
|
parent = parent.parentElement;
|
|
113
113
|
}
|
|
114
|
-
item
|
|
114
|
+
whenHydrated(item, () => {
|
|
115
|
+
item.selected = selected;
|
|
116
|
+
item.focusable = focusable;
|
|
117
|
+
item.level = level;
|
|
118
|
+
});
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
121
|
move(from, delta) {
|
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.53.
|
|
4
|
+
"version": "0.53.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.53.
|
|
50
|
-
"@fusion.dev/helix-icons": "0.53.
|
|
49
|
+
"@fusion.dev/helix-core": "0.53.1",
|
|
50
|
+
"@fusion.dev/helix-icons": "0.53.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.53.
|
|
56
|
-
"@fusion.dev/helix-testing": "0.53.
|
|
55
|
+
"@fusion.dev/helix-cem": "0.53.1",
|
|
56
|
+
"@fusion.dev/helix-testing": "0.53.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",
|