@fusion.dev/helix-headless 0.22.0 → 0.23.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
CHANGED
|
@@ -38926,6 +38926,23 @@
|
|
|
38926
38926
|
"default": "false",
|
|
38927
38927
|
"description": "Whether anything is slotted into `submenu`. Read after first render.\n\nState rather than a getter over `children`. A getter is read during\n`render()` with nothing to re-read it, so a submenu arriving after the\nrow's first render produces no caret and no `aria-haspopup` while selecting\nthe row still drills in, which is what a framework filling a submenu on\ndemand hits every time. `Tree.Item` backs `branch` the same way for the\nsame reason.\n\nKept in step by `readSubmenu`, which is bound in the template rather than\nonce at first render. See the note there."
|
|
38928
38928
|
},
|
|
38929
|
+
{
|
|
38930
|
+
"kind": "field",
|
|
38931
|
+
"name": "start",
|
|
38932
|
+
"type": {
|
|
38933
|
+
"text": "boolean"
|
|
38934
|
+
},
|
|
38935
|
+
"privacy": "private",
|
|
38936
|
+
"default": "false",
|
|
38937
|
+
"description": "Whether anything is slotted before the label.\n\nThe leading box is drawn only where there is something to put in it, the\nsame way the trailing mark is. What keeps the labels on one edge is the\ntrack the menu reserves for the page, not a box on every row, so a row with\nnothing here takes no box and still lines up with its neighbors.\n\nRead from the slot rather than from the light DOM, because the light DOM\nanswer is not reactive: a consumer who adds an icon to one row after first\nrender changes nothing this element is told about, and `slotchange` is\nexactly the thing that is."
|
|
38938
|
+
},
|
|
38939
|
+
{
|
|
38940
|
+
"kind": "field",
|
|
38941
|
+
"name": "readStart",
|
|
38942
|
+
"privacy": "private",
|
|
38943
|
+
"readonly": true,
|
|
38944
|
+
"description": "Re-reads whether anything is before the label.\n\nThe slot itself is always rendered and it is the box around it that comes\nand goes, which is what makes this safe where `readSubmenu` needed the note\nit carries: there is one `slot[name=\"start\"]` in one template branch, so it\nis never rebuilt and never fires on its way out."
|
|
38945
|
+
},
|
|
38929
38946
|
{
|
|
38930
38947
|
"kind": "field",
|
|
38931
38948
|
"name": "readSubmenu",
|
|
@@ -39528,6 +39545,17 @@
|
|
|
39528
39545
|
"privacy": "private",
|
|
39529
39546
|
"readonly": true
|
|
39530
39547
|
},
|
|
39548
|
+
{
|
|
39549
|
+
"kind": "method",
|
|
39550
|
+
"name": "markColumns",
|
|
39551
|
+
"privacy": "private",
|
|
39552
|
+
"return": {
|
|
39553
|
+
"type": {
|
|
39554
|
+
"text": "void"
|
|
39555
|
+
}
|
|
39556
|
+
},
|
|
39557
|
+
"description": "Says whether the page showing reserves a column either side of its labels.\n\n**A fact no row can answer for itself**, which is why the columns were\nunconditional. A row knows about its own start slot and nothing about its\nneighbors, so a page where one row has an icon and the rest do not draws two\ndifferent left edges for its text. Reserving on every row instead charged\nevery menu for a column nothing in it was ever going to fill: measured on a\npage of two plain rows, the text started 36 pixels into the row, of which 12\nwas the row's own padding and 24 a leading column and its seam. The same\npage now starts at 12, and a page where one row does have a mark still\nstarts both of its rows at 36.\n\n**Per page rather than per menu**, because a submenu is a different set of\nrows: a page of checkable filters under a page of plain ones reserves on one\nand not on the other, and asking the whole tree would hand the plain page\nthe filters' column. `Notifications` answers the same question for its media\ncolumn the same way, and for the same reason.\n\nRead off the light DOM rather than off the rows, so it does not depend on\nwhich children have upgraded by the time this runs."
|
|
39558
|
+
},
|
|
39531
39559
|
{
|
|
39532
39560
|
"kind": "method",
|
|
39533
39561
|
"name": "mark",
|
|
@@ -144,7 +144,30 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
144
144
|
* once at first render. See the note there.
|
|
145
145
|
*/
|
|
146
146
|
private submenu;
|
|
147
|
+
/**
|
|
148
|
+
* Whether anything is slotted before the label.
|
|
149
|
+
*
|
|
150
|
+
* The leading box is drawn only where there is something to put in it, the
|
|
151
|
+
* same way the trailing mark is. What keeps the labels on one edge is the
|
|
152
|
+
* track the menu reserves for the page, not a box on every row, so a row with
|
|
153
|
+
* nothing here takes no box and still lines up with its neighbors.
|
|
154
|
+
*
|
|
155
|
+
* Read from the slot rather than from the light DOM, because the light DOM
|
|
156
|
+
* answer is not reactive: a consumer who adds an icon to one row after first
|
|
157
|
+
* render changes nothing this element is told about, and `slotchange` is
|
|
158
|
+
* exactly the thing that is.
|
|
159
|
+
*/
|
|
160
|
+
private start;
|
|
147
161
|
protected firstUpdated(changed: PropertyValues): void;
|
|
162
|
+
/**
|
|
163
|
+
* Re-reads whether anything is before the label.
|
|
164
|
+
*
|
|
165
|
+
* The slot itself is always rendered and it is the box around it that comes
|
|
166
|
+
* and goes, which is what makes this safe where `readSubmenu` needed the note
|
|
167
|
+
* it carries: there is one `slot[name="start"]` in one template branch, so it
|
|
168
|
+
* is never rebuilt and never fires on its way out.
|
|
169
|
+
*/
|
|
170
|
+
private readonly readStart;
|
|
148
171
|
/**
|
|
149
172
|
* Re-reads whether anything is in the submenu, from whichever slot is current.
|
|
150
173
|
*
|
|
@@ -24,6 +24,11 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
24
24
|
this.value = '';
|
|
25
25
|
this.isMenuItem = true;
|
|
26
26
|
this.submenu = false;
|
|
27
|
+
this.start = false;
|
|
28
|
+
this.readStart = () => {
|
|
29
|
+
const slot = this.renderRoot.querySelector('slot[name="start"]');
|
|
30
|
+
this.start = (slot?.assignedNodes().length ?? 0) > 0;
|
|
31
|
+
};
|
|
27
32
|
this.readSubmenu = () => {
|
|
28
33
|
const slot = this.renderRoot.querySelector('slot[name="submenu"]');
|
|
29
34
|
this.submenu = (slot?.assignedElements().length ?? 0) > 0;
|
|
@@ -53,6 +58,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
53
58
|
firstUpdated(changed) {
|
|
54
59
|
super.firstUpdated(changed);
|
|
55
60
|
this.readSubmenu();
|
|
61
|
+
this.readStart();
|
|
56
62
|
}
|
|
57
63
|
focus() {
|
|
58
64
|
this.renderRoot.querySelector('[part="item"]')?.focus();
|
|
@@ -83,7 +89,9 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
83
89
|
const linked = this.href !== '' && !this.disabled;
|
|
84
90
|
const role = this.checkable || this.checked ? 'menuitemcheckbox' : 'menuitem';
|
|
85
91
|
const checkedState = this.checkable || this.checked ? (this.checked ? 'true' : 'false') : nothing;
|
|
86
|
-
const inside = html `<span part="start"
|
|
92
|
+
const inside = html `<span part="start" ?data-empty=${!this.start}
|
|
93
|
+
><slot name="start" @slotchange=${this.readStart}></slot
|
|
94
|
+
></span>
|
|
87
95
|
<slot></slot>
|
|
88
96
|
${submenu
|
|
89
97
|
? html `<svg
|
|
@@ -104,7 +112,8 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
104
112
|
>
|
|
105
113
|
${unsafeSVG(check.content)}
|
|
106
114
|
</svg>`
|
|
107
|
-
:
|
|
115
|
+
:
|
|
116
|
+
nothing}`;
|
|
108
117
|
return html `${linked
|
|
109
118
|
? html `<a
|
|
110
119
|
part="item"
|
|
@@ -165,6 +174,9 @@ __decorate([
|
|
|
165
174
|
__decorate([
|
|
166
175
|
state()
|
|
167
176
|
], HelixHeadlessMenuItem.prototype, "submenu", void 0);
|
|
177
|
+
__decorate([
|
|
178
|
+
state()
|
|
179
|
+
], HelixHeadlessMenuItem.prototype, "start", void 0);
|
|
168
180
|
HelixHeadlessMenuItem = __decorate([
|
|
169
181
|
customElement('helix-headless-menu-item')
|
|
170
182
|
], HelixHeadlessMenuItem);
|
|
@@ -126,6 +126,29 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
126
126
|
connectedCallback(): void;
|
|
127
127
|
disconnectedCallback(): void;
|
|
128
128
|
protected updated(changed: PropertyValues): void;
|
|
129
|
+
/**
|
|
130
|
+
* Says whether the page showing reserves a column either side of its labels.
|
|
131
|
+
*
|
|
132
|
+
* **A fact no row can answer for itself**, which is why the columns were
|
|
133
|
+
* unconditional. A row knows about its own start slot and nothing about its
|
|
134
|
+
* neighbors, so a page where one row has an icon and the rest do not draws two
|
|
135
|
+
* different left edges for its text. Reserving on every row instead charged
|
|
136
|
+
* every menu for a column nothing in it was ever going to fill: measured on a
|
|
137
|
+
* page of two plain rows, the text started 36 pixels into the row, of which 12
|
|
138
|
+
* was the row's own padding and 24 a leading column and its seam. The same
|
|
139
|
+
* page now starts at 12, and a page where one row does have a mark still
|
|
140
|
+
* starts both of its rows at 36.
|
|
141
|
+
*
|
|
142
|
+
* **Per page rather than per menu**, because a submenu is a different set of
|
|
143
|
+
* rows: a page of checkable filters under a page of plain ones reserves on one
|
|
144
|
+
* and not on the other, and asking the whole tree would hand the plain page
|
|
145
|
+
* the filters' column. `Notifications` answers the same question for its media
|
|
146
|
+
* column the same way, and for the same reason.
|
|
147
|
+
*
|
|
148
|
+
* Read off the light DOM rather than off the rows, so it does not depend on
|
|
149
|
+
* which children have upgraded by the time this runs.
|
|
150
|
+
*/
|
|
151
|
+
private markColumns;
|
|
129
152
|
/**
|
|
130
153
|
* Walks the tree marking which rows are on the page showing.
|
|
131
154
|
*
|
|
@@ -159,6 +159,12 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
159
159
|
}
|
|
160
160
|
this.mark(item, this.drilldown.atRoot);
|
|
161
161
|
}
|
|
162
|
+
this.markColumns();
|
|
163
|
+
}
|
|
164
|
+
markColumns() {
|
|
165
|
+
const rows = this.drilldown.rows;
|
|
166
|
+
this.toggleState('start', rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
|
|
167
|
+
this.toggleState('mark', rows.some((row) => row.checkable || row.checked || this.drilldown.hasPage(row)));
|
|
162
168
|
}
|
|
163
169
|
mark(item, onPage) {
|
|
164
170
|
item.onPage = onPage;
|
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.23.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ross Sheppard <ross.sheppard@fusion.dev>",
|
|
7
7
|
"repository": {
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
},
|
|
46
46
|
"bugs": "https://github.com/project-fusion/fusion/issues",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@fusion.dev/helix-core": "0.
|
|
49
|
-
"@fusion.dev/helix-icons": "0.
|
|
48
|
+
"@fusion.dev/helix-core": "0.23.0",
|
|
49
|
+
"@fusion.dev/helix-icons": "0.23.0",
|
|
50
50
|
"uqr": "^0.1.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
54
|
-
"@fusion.dev/helix-cem": "0.
|
|
55
|
-
"@fusion.dev/helix-testing": "0.
|
|
54
|
+
"@fusion.dev/helix-cem": "0.23.0",
|
|
55
|
+
"@fusion.dev/helix-testing": "0.23.0",
|
|
56
56
|
"@fusion.dev/scripts": "0.0.0-dev",
|
|
57
57
|
"@happy-dom/global-registrator": "^20.0.0",
|
|
58
58
|
"@lit-labs/ssr": "^3.3.0",
|