@fusion.dev/helix-headless 0.2.0 → 0.3.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 +234 -234
- package/dist/accordion/item/accordion-item.js +0 -108
- package/dist/accordion/root/accordion-root.js +0 -33
- package/dist/announcement/root/announcement-root.js +1 -76
- package/dist/app-header/root/app-header-root.js +0 -84
- package/dist/avatar/group/avatar-group.js +0 -77
- package/dist/avatar/root/avatar-root.js +0 -72
- package/dist/badge/root/badge-root.js +0 -67
- package/dist/bar-chart/root/bar-chart-root.js +3 -263
- package/dist/blockquote/root/blockquote-root.js +0 -46
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
- package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
- package/dist/button/root/button-root.js +0 -215
- package/dist/button-group/root/button-group-root.js +0 -30
- package/dist/callout/root/callout-root.js +0 -52
- package/dist/card/root/card-root.js +0 -41
- package/dist/carousel/root/carousel-root.d.ts +2 -2
- package/dist/carousel/root/carousel-root.js +2 -362
- package/dist/chat/composer/chat-composer.js +0 -131
- package/dist/chat/message/chat-message.js +0 -137
- package/dist/chat/root/chat-root.js +0 -193
- package/dist/checkbox/root/checkbox-root.js +0 -113
- package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
- package/dist/code/root/code-root.js +0 -26
- package/dist/code-block/root/code-block-root.js +0 -100
- package/dist/color-input/root/color-input-root.js +0 -217
- package/dist/color-picker/color.js +0 -69
- package/dist/color-picker/root/color-picker-root.js +0 -272
- package/dist/combobox/root/combobox-root.js +0 -428
- package/dist/command/root/command-root.js +0 -177
- package/dist/copy-button/root/copy-button-root.js +0 -134
- package/dist/data-table/root/data-table-root.js +0 -336
- package/dist/data-table/root/order.js +0 -100
- package/dist/date-input/root/date-input-root.js +1 -398
- package/dist/date-picker/root/date-picker-root.js +0 -358
- package/dist/dialog/root/dialog-root.js +0 -189
- package/dist/divider/root/divider-root.js +0 -71
- package/dist/donut-chart/root/donut-chart-root.js +0 -214
- package/dist/drawer/root/drawer-root.js +0 -116
- package/dist/drilldown/item/drilldown-item.js +0 -82
- package/dist/drilldown/root/drilldown-root.js +0 -91
- package/dist/element-events.generated.js +0 -5
- package/dist/empty/root/empty-root.js +0 -35
- package/dist/file-input/root/file-input-root.js +0 -232
- package/dist/format-date/root/format-date-root.js +0 -95
- package/dist/heatmap/root/buckets.js +0 -57
- package/dist/heatmap/root/heatmap-root.js +0 -313
- package/dist/icon/root/icon-root.js +0 -48
- package/dist/icon-button/root/icon-button-root.js +0 -30
- package/dist/image/root/image-root.js +0 -174
- package/dist/index.js +0 -9
- package/dist/input/root/input-root.js +0 -228
- package/dist/kbd/root/kbd-root.js +0 -117
- package/dist/legend/root/legend-root.js +0 -73
- package/dist/line-chart/root/line-chart-root.js +0 -172
- package/dist/list/item/list-item.js +0 -92
- package/dist/list/root/list-root.js +0 -26
- package/dist/listbox/root/listbox-root.js +2 -429
- package/dist/loader/root/loader-root.js +0 -40
- package/dist/megamenu/root/megamenu-root.js +0 -284
- package/dist/menu/item/menu-item.js +1 -221
- package/dist/menu/root/menu-root.js +1 -171
- package/dist/meter/root/meter-root.js +1 -152
- package/dist/navigation/root/navigation-root.js +0 -56
- package/dist/notifications/notification/notification.js +0 -159
- package/dist/notifications/root/notifications-root.js +0 -148
- package/dist/number-input/root/number-input-root.js +1 -263
- package/dist/option/group/option-group.js +0 -26
- package/dist/option/root/option-root.js +0 -157
- package/dist/outline/root/outline-root.js +0 -292
- package/dist/pagination/root/pagination-root.js +0 -361
- package/dist/pagination/size/pagination-size.js +0 -104
- package/dist/pagination/summary/pagination-summary.js +0 -90
- package/dist/pin-input/root/pin-input-root.js +0 -276
- package/dist/popover/root/popover-root.js +0 -103
- package/dist/progress/root/progress-root.js +1 -133
- package/dist/qr-code/root/qr-code-root.js +0 -96
- package/dist/quote/root/quote-root.js +0 -25
- package/dist/radio/group/radio-group.js +0 -81
- package/dist/radio/root/radio-root.js +0 -85
- package/dist/rating/root/rating-root.js +0 -220
- package/dist/scroll-area/root/scroll-area-root.js +0 -100
- package/dist/select/root/select-root.js +2 -218
- package/dist/skeleton/root/skeleton-root.js +0 -20
- package/dist/slider/root/slider-root.js +1 -267
- package/dist/sparkline/root/sparkline-root.js +3 -164
- package/dist/speed-dial/item/speed-dial-item.js +0 -52
- package/dist/speed-dial/root/speed-dial-root.js +1 -116
- package/dist/splitter/root/splitter-root.js +0 -489
- package/dist/stat/group/stat-group.js +0 -18
- package/dist/stat/root/stat-root.js +0 -23
- package/dist/steps/root/steps-root.js +0 -62
- package/dist/steps/step/step.js +0 -131
- package/dist/switch/root/switch-root.js +0 -80
- package/dist/table/cell/table-cell.js +0 -23
- package/dist/table/column/table-column.js +0 -158
- package/dist/table/root/table-root.js +0 -82
- package/dist/table/row/table-row.js +0 -21
- package/dist/tabs/panel/tab-panel.js +0 -49
- package/dist/tabs/root/tabs-root.js +0 -98
- package/dist/tabs/tab/tab.js +0 -59
- package/dist/tag/root/tag-root.js +0 -31
- package/dist/textarea/root/textarea-root.js +0 -162
- package/dist/time-input/root/time-input-root.js +0 -283
- package/dist/time-picker/root/time-picker-root.js +0 -326
- package/dist/timeline/item/timeline-item.js +0 -66
- package/dist/timeline/root/timeline-root.js +0 -41
- package/dist/toast/item/toast-item.js +0 -165
- package/dist/toast/root/toast-root.js +0 -51
- package/dist/toggle/group/toggle-group.js +0 -150
- package/dist/toggle/root/toggle-root.js +0 -78
- package/dist/toolbar/root/toolbar-root.js +0 -116
- package/dist/tooltip/root/tooltip-root.js +0 -427
- package/dist/tour/root/tour-root.js +0 -130
- package/dist/tour/stop/tour-stop.js +1 -127
- package/dist/tree/item/tree-item.js +0 -198
- package/dist/tree/root/tree-root.js +0 -97
- package/dist/trend/root/trend-root.js +0 -53
- package/dist/video/root/video-root.d.ts +8 -12
- package/dist/video/root/video-root.js +5 -756
- package/dist/video-playlist/item/video-playlist-item.js +1 -90
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
- package/dist/video-playlist/root/video-playlist-root.js +0 -113
- package/dist/virtual-list/root/virtual-list-root.js +0 -105
- package/jsx.d.ts +1408 -2576
- package/package.json +6 -6
- package/vue.d.ts +1 -1
|
@@ -7,82 +7,18 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, MixinCollection } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary Tabular data laid out on a grid.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* A grid rather than a `<table>`, so every column lines up across rows while
|
|
15
|
-
* each row stays a real element that can be styled, hidden, or made
|
|
16
|
-
* interactive. Table layout gives column alignment and takes away almost
|
|
17
|
-
* everything else; grid gives both.
|
|
18
|
-
*
|
|
19
|
-
* The cost is that the roles have to be declared, since a grid of divs is not a
|
|
20
|
-
* table to anyone reading it. `table`, `row`, `cell`, and `columnheader` are
|
|
21
|
-
* all set explicitly, and rows are `display: contents` so their cells become
|
|
22
|
-
* items of the one grid that does the aligning.
|
|
23
|
-
*
|
|
24
|
-
* The properties below are written by this component and read by whoever draws
|
|
25
|
-
* it. A consumer sets none of them: they carry a number this element computed
|
|
26
|
-
* and CSS cannot, which is what lets a stylesheet on this tier draw the thing
|
|
27
|
-
* without measuring anything itself.
|
|
28
|
-
*
|
|
29
|
-
* @since 0.1.0
|
|
30
|
-
* @status experimental
|
|
31
|
-
* @category data
|
|
32
|
-
* @aliases DataTable, Grid table
|
|
33
|
-
* @documentation https://helix.fusion.dev/components/table
|
|
34
|
-
*
|
|
35
|
-
* @slot - The table's rows.
|
|
36
|
-
*
|
|
37
|
-
* @csspart empty - Shown when there are no rows to show.
|
|
38
|
-
* @csspart head - The row the column headings sit in. `display: contents`, so it takes no space.
|
|
39
|
-
* @csspart loading - Shown while the rows are still on their way.
|
|
40
|
-
* @csspart table - The grid.
|
|
41
|
-
*
|
|
42
|
-
* @cssproperty --table-columns - Track list for the columns.
|
|
43
|
-
* @cssproperty --table-template - The column track list this element worked out from the columns declared in it. Written by the component, read by the sheet that paints: a stylesheet takes it to lay the grid out, and setting it does nothing, because the component writes it inline on the very element that reads it and an inline declaration beats any sheet. `--table-columns` is the one to set.
|
|
44
|
-
*/
|
|
45
10
|
let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixElement) {
|
|
46
11
|
constructor() {
|
|
47
12
|
super(...arguments);
|
|
48
|
-
/**
|
|
49
|
-
* Column track list, as CSS. Deprecated: declare columns instead.
|
|
50
|
-
*
|
|
51
|
-
* Kept for a table that has no `helix-table-column` children, and ignored the
|
|
52
|
-
* moment it has one. A raw track list is the right shape for the value and
|
|
53
|
-
* the wrong place for it: the headings and the widths were two halves of one
|
|
54
|
-
* fact held in two places, and nothing checked that they agreed. Worse, the
|
|
55
|
-
* value is CSS, so handing it headings by mistake produced an invalid track
|
|
56
|
-
* list, which a grid ignores in silence: the table collapsed to one column
|
|
57
|
-
* and said nothing. Columns carry both halves now.
|
|
58
|
-
*/
|
|
59
13
|
this.columns = '';
|
|
60
|
-
/**
|
|
61
|
-
* Accessible name for the table.
|
|
62
|
-
*/
|
|
63
14
|
this.label = '';
|
|
64
|
-
/* Columns arrive from the light DOM, which changes no property here, so
|
|
65
|
-
nothing would schedule the render that reads them. */
|
|
66
15
|
this.handleSlotChange = () => {
|
|
67
16
|
this.requestUpdate();
|
|
68
17
|
};
|
|
69
18
|
}
|
|
70
|
-
/** The columns declared as children, in the order they are written. */
|
|
71
19
|
get tableColumns() {
|
|
72
|
-
/* `?? []` is the server. Lit's element shim carries attributes and not a
|
|
73
|
-
tree, so `children` is undefined there, and this getter is reached from
|
|
74
|
-
`template`, which is read during `render`: spreading it throws and takes
|
|
75
|
-
the whole SSR render down with it. */
|
|
76
20
|
return [...(this.children ?? [])].filter((child) => 'isTableColumn' in child && child.isTableColumn === true);
|
|
77
21
|
}
|
|
78
|
-
/**
|
|
79
|
-
* The track list, from the columns if there are any.
|
|
80
|
-
*
|
|
81
|
-
* Read from the DOM rather than kept, so adding or removing a column needs
|
|
82
|
-
* nothing else to be told. Falls back to the `columns` string for a table
|
|
83
|
-
* written before columns existed, and to a single track for one that declares
|
|
84
|
-
* neither.
|
|
85
|
-
*/
|
|
86
22
|
get template() {
|
|
87
23
|
const declared = this.tableColumns;
|
|
88
24
|
if (declared.length > 0) {
|
|
@@ -90,24 +26,6 @@ let HelixHeadlessTable = class HelixHeadlessTable extends MixinCollection(HelixE
|
|
|
90
26
|
}
|
|
91
27
|
return this.columns;
|
|
92
28
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Nothing is assigned by name, so the columns can be put in a row.
|
|
95
|
-
*
|
|
96
|
-
* The table pattern is `table > row > columnheader`, and a header cell
|
|
97
|
-
* landing directly under `role="table"` with no owning row is invalid: it is
|
|
98
|
-
* a cell belonging to nothing. The columns are ordinary children the consumer
|
|
99
|
-
* writes with no `slot`, so the only way to put a wrapper around them without
|
|
100
|
-
* changing what a consumer writes is to assign them here.
|
|
101
|
-
*
|
|
102
|
-
* Manual rather than a `slot` attribute this element would set on the
|
|
103
|
-
* consumer's own markup, which is the change a React consumer's hydration
|
|
104
|
-
* reports and does not patch up. Assignment writes nothing at all.
|
|
105
|
-
*
|
|
106
|
-
* Safe against the server because this repo ships no declarative shadow DOM:
|
|
107
|
-
* the shadow root exists only after upgrade, and the assignment below runs in
|
|
108
|
-
* that same update. There is no window in which the shadow is present and the
|
|
109
|
-
* assignment has not happened.
|
|
110
|
-
*/
|
|
111
29
|
static { this.shadowRootOptions = {
|
|
112
30
|
...HelixElement.shadowRootOptions,
|
|
113
31
|
slotAssignment: 'manual',
|
|
@@ -6,30 +6,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
|
-
/**
|
|
10
|
-
* @summary One row of a table.
|
|
11
|
-
*
|
|
12
|
-
* @description
|
|
13
|
-
* `display: contents` in the styled tier, so the row's cells become items of
|
|
14
|
-
* the table's own grid and every column lines up across every row. The row
|
|
15
|
-
* still exists as an element, which is what a `<table>` gives away when its
|
|
16
|
-
* cells are laid out by the table algorithm.
|
|
17
|
-
*
|
|
18
|
-
* @since 0.1.0
|
|
19
|
-
* @status experimental
|
|
20
|
-
* @aliases Row, TableRow
|
|
21
|
-
* @documentation https://helix.fusion.dev/components/table
|
|
22
|
-
*
|
|
23
|
-
* @slot - The row's cells.
|
|
24
|
-
*/
|
|
25
9
|
let HelixHeadlessTableRow = class HelixHeadlessTableRow extends HelixElement {
|
|
26
10
|
willUpdate(changed) {
|
|
27
11
|
super.willUpdate(changed);
|
|
28
|
-
// On the host, and required: `display: contents` removes the row's box but
|
|
29
|
-
// not the row from the table's structure. Without this the table has cells
|
|
30
|
-
// and no rows, which is a broken table rather than a plain one.
|
|
31
|
-
//
|
|
32
|
-
// Set in `willUpdate`, which the server does call, unlike `connectedCallback`.
|
|
33
12
|
hostAria(this.internals, { role: 'row' });
|
|
34
13
|
}
|
|
35
14
|
render() {
|
|
@@ -7,62 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary The content shown for one tab.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Hidden rather than unmounted when inactive, so anything typed into a panel is
|
|
15
|
-
* still there when the reader comes back to it, and so the content is in the
|
|
16
|
-
* server's markup for a crawler regardless of which tab is open.
|
|
17
|
-
*
|
|
18
|
-
* @since 0.1.0
|
|
19
|
-
* @status experimental
|
|
20
|
-
* @aliases TabPanel
|
|
21
|
-
* @documentation https://helix.fusion.dev/components/tabs
|
|
22
|
-
*
|
|
23
|
-
* @slot - The panel's content.
|
|
24
|
-
*
|
|
25
|
-
* @csspart panel - The panel container.
|
|
26
|
-
*/
|
|
27
10
|
let HelixHeadlessTabPanel = class HelixHeadlessTabPanel extends HelixElement {
|
|
28
11
|
constructor() {
|
|
29
12
|
super(...arguments);
|
|
30
|
-
/**
|
|
31
|
-
* The name of the panel, which is the text of the tab that opens it.
|
|
32
|
-
*
|
|
33
|
-
* Set by the root, which is the only thing that sees both. No attribute,
|
|
34
|
-
* because it is coordination between elements rather than anything a consumer
|
|
35
|
-
* writes: the tab already says it.
|
|
36
|
-
*
|
|
37
|
-
* A string and not a reference: an IDREF resolves only in the tree it is
|
|
38
|
-
* written in, and an element reference cannot reach a sibling shadow tree
|
|
39
|
-
* either, so `aria-labelledby` to the tab's own id leaves the panel with no
|
|
40
|
-
* accessible name. `aria-label` on a `tabpanel` is how the pattern names one
|
|
41
|
-
* anyway.
|
|
42
|
-
*/
|
|
43
13
|
this.label = '';
|
|
44
|
-
/**
|
|
45
|
-
* Whether this panel is the visible one.
|
|
46
|
-
*/
|
|
47
14
|
this.selected = false;
|
|
48
|
-
/**
|
|
49
|
-
* Pairs the panel with the tab of the same value.
|
|
50
|
-
*/
|
|
51
15
|
this.value = '';
|
|
52
16
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Mirrors the chosen state where a stylesheet can select on it.
|
|
55
|
-
*
|
|
56
|
-
* **A custom state, never a reflected `selected` attribute.** The parent
|
|
57
|
-
* writes this, so reflecting it would put the element's own bookkeeping onto
|
|
58
|
-
* markup a consumer wrote and their server never sent, which React reports as
|
|
59
|
-
* a hydration mismatch and which a serialized element pasted back declares as
|
|
60
|
-
* a choice nobody made. `ElementInternals` holds it instead.
|
|
61
|
-
*
|
|
62
|
-
* The attribute still seeds the property, which is what
|
|
63
|
-
* `<helix-tab-panel selected>` in a consumer's markup means. Only the write
|
|
64
|
-
* back is gone.
|
|
65
|
-
*/
|
|
66
17
|
willUpdate(changed) {
|
|
67
18
|
super.willUpdate(changed);
|
|
68
19
|
this.toggleState('selected', this.selected);
|
|
@@ -9,87 +9,24 @@ import { html } from 'lit';
|
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { HelixHeadlessTabPanel } from '../panel/tab-panel.js';
|
|
11
11
|
import { HelixHeadlessTab } from '../tab/tab.js';
|
|
12
|
-
/**
|
|
13
|
-
* @summary A row of tabs and the panel each one shows.
|
|
14
|
-
*
|
|
15
|
-
* @description
|
|
16
|
-
* Selection is coordinated by event, the same shape the accordion and the tree
|
|
17
|
-
* use: a tab dispatches `helix-select` and the root pairs it with the panel of
|
|
18
|
-
* the same value. Nothing registers with anything, so tabs added later work
|
|
19
|
-
* without the root being told.
|
|
20
|
-
*
|
|
21
|
-
* The tab list is a single tab stop, moved with the arrow keys through
|
|
22
|
-
* `KeyboardNavigationController`, the same one the radio and toggle groups use.
|
|
23
|
-
* Selection follows focus, which is the right default for a small set where
|
|
24
|
-
* showing a panel is cheap; a set where it is not should use `manual`.
|
|
25
|
-
*
|
|
26
|
-
* @since 0.1.0
|
|
27
|
-
* @status experimental
|
|
28
|
-
* @category navigation
|
|
29
|
-
* @aliases TabList, Segmented control
|
|
30
|
-
* @documentation https://helix.fusion.dev/components/tabs
|
|
31
|
-
*
|
|
32
|
-
* @slot - The tabs.
|
|
33
|
-
* @slot panels - The panels.
|
|
34
|
-
*
|
|
35
|
-
* @event {HelixActivateEvent} helix-activate - Fired once the current one has moved, so the element already holds it.
|
|
36
|
-
* @event {HelixBeforeActivateEvent} helix-before-activate - Fired before the current tab moves, and cancelable. The only way to refuse one.
|
|
37
|
-
* @event {HelixSelectEvent} helix-select - Fired when a tab is chosen. Dispatched by the tab and listened for here, because the root is what a consumer holds. Cancelable: refusing it takes the decision and nothing moves.
|
|
38
|
-
*
|
|
39
|
-
* @csspart indicator - The mark that travels to the selected tab.
|
|
40
|
-
* @csspart panels - The panel container.
|
|
41
|
-
* @csspart tabs - The tab list.
|
|
42
|
-
*/
|
|
43
12
|
let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(HelixElement) {
|
|
44
13
|
constructor() {
|
|
45
14
|
super(...arguments);
|
|
46
|
-
/* Arrow keys, Home and End. Same controller as the radio group and the toggle
|
|
47
|
-
group: `origin: 'focus'` because `manual` lets focus and selection diverge,
|
|
48
|
-
and `selects` because `manual` is what decides whether arriving chooses. */
|
|
49
15
|
this.keys = new KeyboardNavigationController(this, {
|
|
50
16
|
items: () => this.tabs,
|
|
51
17
|
origin: 'focus',
|
|
52
18
|
selects: () => !this.manual,
|
|
53
19
|
});
|
|
54
|
-
/* The mark. Same controller as the nav bar, the toggle group and the page nav. */
|
|
55
20
|
this.indicator = new IndicatorController(this, {
|
|
56
21
|
active: () => this.tabs.find((tab) => tab.selected),
|
|
57
22
|
indicator: () => this.renderRoot?.querySelector('[part="indicator"]'),
|
|
58
23
|
surface: () => this.renderRoot?.querySelector('[part="tabs"]'),
|
|
59
24
|
});
|
|
60
|
-
/**
|
|
61
|
-
* Accessible name for the tab list.
|
|
62
|
-
*/
|
|
63
25
|
this.label = 'Tabs';
|
|
64
|
-
/**
|
|
65
|
-
* Whether arrowing to a tab also selects it.
|
|
66
|
-
*
|
|
67
|
-
* Automatic is the default because showing a panel is usually cheap. Set this
|
|
68
|
-
* where it is not, so a reader arrowing past four tabs does not load four
|
|
69
|
-
* panels on the way.
|
|
70
|
-
*/
|
|
71
26
|
this.manual = false;
|
|
72
|
-
/* Through the mixin, so a consumer can refuse the change. Moving `active`
|
|
73
|
-
straight from the event leaves `helix-before-activate` an announcement
|
|
74
|
-
nobody can answer. */
|
|
75
27
|
this.handleSelection = (event) => {
|
|
76
28
|
this.requestActive(event.detail.value);
|
|
77
29
|
};
|
|
78
|
-
/**
|
|
79
|
-
* The set changed: pair it up, watch it, and measure it.
|
|
80
|
-
*
|
|
81
|
-
* All three, and dropping `place()` leaves an indicator unplaced for the life
|
|
82
|
-
* of the page. The order of events is what does it: the host renders and
|
|
83
|
-
* measures before its tabs have upgraded, so there is no selected tab to
|
|
84
|
-
* measure and `place` refuses; the ResizeObserver's one initial callback
|
|
85
|
-
* lands in that same gap; and then the tabs upgrade, `slotchange` pairs them,
|
|
86
|
-
* nothing measures, and nothing ever will again, because the host's size
|
|
87
|
-
* never changes.
|
|
88
|
-
*
|
|
89
|
-
* Invisible in every example, because a tab strip that is only tabs is
|
|
90
|
-
* exactly as tall as its tabs, so a mark at the top of the strip is a mark on
|
|
91
|
-
* the tab. It shows only where something gives the strip a height of its own.
|
|
92
|
-
*/
|
|
93
30
|
this.handleSlotChange = () => {
|
|
94
31
|
this.sync();
|
|
95
32
|
this.watch();
|
|
@@ -102,19 +39,12 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
102
39
|
get panels() {
|
|
103
40
|
return [...this.children].filter((child) => child instanceof HelixHeadlessTabPanel);
|
|
104
41
|
}
|
|
105
|
-
/** Pushes the current value down to whichever tab and panel own it. */
|
|
106
42
|
sync() {
|
|
107
43
|
const tabs = this.tabs;
|
|
108
|
-
/* Through the mixin, which also skips a disabled first tab. A hand-written
|
|
109
|
-
fallback to the first child shows a disabled tab as current and opens its
|
|
110
|
-
panel with it. */
|
|
111
44
|
const value = this.resolveActive(tabs)?.value ?? '';
|
|
112
45
|
for (const tab of tabs) {
|
|
113
46
|
tab.selected = tab.value === value;
|
|
114
47
|
}
|
|
115
|
-
/* The panel's name comes from its tab, and only this element sees both.
|
|
116
|
-
Read from the tab's light DOM, which is where the consumer wrote the
|
|
117
|
-
words, so it is whatever the reader sees on the tab. */
|
|
118
48
|
const names = new Map(tabs.map((tab) => [tab.value, (tab.textContent ?? '').trim()]));
|
|
119
49
|
for (const panel of this.panels) {
|
|
120
50
|
panel.selected = panel.value === value;
|
|
@@ -124,14 +54,6 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
124
54
|
connectedCallback() {
|
|
125
55
|
super.connectedCallback();
|
|
126
56
|
this.addEventListener('helix-select', this.handleSelection);
|
|
127
|
-
/* The observer is rebuilt here, because nothing else does it.
|
|
128
|
-
*
|
|
129
|
-
* `firstUpdated` runs once for the life of the element and
|
|
130
|
-
* `disconnectedCallback` runs every time it moves, so a framework
|
|
131
|
-
* reordering a list left this permanently dead: the observer gone, nothing
|
|
132
|
-
* to rebuild it, and the component still rendering as though it were
|
|
133
|
-
* watching. Guarded on `hasUpdated`, because the first connection happens
|
|
134
|
-
* before the first render and there is nothing to watch yet. */
|
|
135
57
|
if (this.hasUpdated) {
|
|
136
58
|
this.measure();
|
|
137
59
|
this.watch();
|
|
@@ -146,36 +68,16 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
146
68
|
firstUpdated(changed) {
|
|
147
69
|
super.firstUpdated(changed);
|
|
148
70
|
this.sync();
|
|
149
|
-
/* Straight away, and again after a frame. The first call is what places it
|
|
150
|
-
in a document that is already laid out; the second covers a first paint
|
|
151
|
-
that has not happened yet, where every tab measures zero and `place`
|
|
152
|
-
refuses rather than writing a wrong position.
|
|
153
|
-
|
|
154
|
-
Not the frame alone: `requestAnimationFrame` does not run in a document
|
|
155
|
-
the browser is not painting, so a tab set rendered in a hidden tab or an
|
|
156
|
-
inactive pane would never place its mark at all and would slide in from
|
|
157
|
-
the origin the moment the page was looked at. */
|
|
158
71
|
this.indicator.place();
|
|
159
72
|
requestAnimationFrame(() => this.indicator.place());
|
|
160
73
|
this.measure();
|
|
161
74
|
this.watch();
|
|
162
75
|
}
|
|
163
|
-
/** The host's own size, which moves every tab and therefore the mark. */
|
|
164
76
|
measure() {
|
|
165
77
|
this.observer?.disconnect();
|
|
166
78
|
this.observer = new ResizeObserver(() => this.indicator.place());
|
|
167
79
|
this.observer.observe(this);
|
|
168
80
|
}
|
|
169
|
-
/**
|
|
170
|
-
* Watches every tab for a change of size.
|
|
171
|
-
*
|
|
172
|
-
* Called again whenever the set changes, because `tabs` reads `this.children`
|
|
173
|
-
* and filters on `instanceof`, so it is empty until the tab elements upgrade.
|
|
174
|
-
* At `firstUpdated` it frequently is empty, and observing nothing is silent.
|
|
175
|
-
*
|
|
176
|
-
* `observe` is idempotent per element, so re-running this costs nothing and
|
|
177
|
-
* needs no bookkeeping about which tabs are already watched.
|
|
178
|
-
*/
|
|
179
81
|
watch() {
|
|
180
82
|
for (const tab of this.tabs) {
|
|
181
83
|
this.observer?.observe(tab);
|
package/dist/tabs/tab/tab.js
CHANGED
|
@@ -7,86 +7,27 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, HelixSelectEvent } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary One tab in a tab list.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* An element rather than a slot because it owns selection and its own tab stop.
|
|
15
|
-
*
|
|
16
|
-
* Its id and the id it controls are both derived from `value`, so the pairing
|
|
17
|
-
* between a tab and its panel survives server rendering. A generated id would
|
|
18
|
-
* differ between the server's render and the client's and break the link, and
|
|
19
|
-
* `aria-controls` pointing at nothing is worse than not being there.
|
|
20
|
-
*
|
|
21
|
-
* @since 0.1.0
|
|
22
|
-
* @status experimental
|
|
23
|
-
* @aliases Tab
|
|
24
|
-
* @documentation https://helix.fusion.dev/components/tabs
|
|
25
|
-
*
|
|
26
|
-
* @slot - The tab's label.
|
|
27
|
-
*
|
|
28
|
-
* @event {HelixSelectEvent} helix-select - Fired when the tab is chosen. Cancelable: refusing it takes the decision and nothing moves.
|
|
29
|
-
*
|
|
30
|
-
* @csspart tab - The tab itself.
|
|
31
|
-
*/
|
|
32
10
|
let HelixHeadlessTab = class HelixHeadlessTab extends HelixElement {
|
|
33
11
|
constructor() {
|
|
34
12
|
super(...arguments);
|
|
35
|
-
/**
|
|
36
|
-
* Whether the tab can be chosen.
|
|
37
|
-
*/
|
|
38
13
|
this.disabled = false;
|
|
39
|
-
/**
|
|
40
|
-
* Whether this tab is the current one.
|
|
41
|
-
*/
|
|
42
14
|
this.selected = false;
|
|
43
|
-
/**
|
|
44
|
-
* Identifies the tab, and pairs it with the panel of the same value.
|
|
45
|
-
*/
|
|
46
15
|
this.value = '';
|
|
47
16
|
}
|
|
48
|
-
/** Moves focus to this tab, for the list's arrow keys. */
|
|
49
17
|
focusItem() {
|
|
50
18
|
this.renderRoot.querySelector('[part="tab"]')?.focus();
|
|
51
19
|
}
|
|
52
|
-
/** Selects this tab and announces it. */
|
|
53
20
|
select() {
|
|
54
21
|
if (this.disabled) {
|
|
55
22
|
return;
|
|
56
23
|
}
|
|
57
24
|
this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
|
|
58
25
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Mirrors the chosen state where a stylesheet can select on it.
|
|
61
|
-
*
|
|
62
|
-
* **A custom state, not a reflected `selected` attribute.** The parent writes
|
|
63
|
-
* this: a listbox choosing a row, a tab strip moving the tab, a radio group
|
|
64
|
-
* answering for its set. Reflected, it puts the element's own bookkeeping onto
|
|
65
|
-
* markup a consumer wrote and a consumer's server never sent, which React
|
|
66
|
-
* reconciles and reports as a mismatch, and which a serialized element pasted
|
|
67
|
-
* back declares as a choice nobody made. `ElementInternals` holds it instead,
|
|
68
|
-
* where it is not in the DOM at all.
|
|
69
|
-
*
|
|
70
|
-
* Reading `selected` is unaffected and so is writing it: the attribute still
|
|
71
|
-
* seeds the property, which is what `<helix-tab selected>` in a consumer's
|
|
72
|
-
* markup means. What stops is the write back.
|
|
73
|
-
*/
|
|
74
26
|
willUpdate(changed) {
|
|
75
27
|
super.willUpdate(changed);
|
|
76
28
|
this.toggleState('selected', this.selected);
|
|
77
29
|
}
|
|
78
30
|
render() {
|
|
79
|
-
/* No `aria-controls`, and no id for one to point at.
|
|
80
|
-
*
|
|
81
|
-
* An IDREF resolves only within the tree it is written in, and the panel's
|
|
82
|
-
* id is rendered inside the panel's *own* shadow root, so an `aria-controls`
|
|
83
|
-
* here would point at nothing on every tab.
|
|
84
|
-
*
|
|
85
|
-
* There is no id form that could work and no element reference either: a
|
|
86
|
-
* reference is valid only into the same tree or a shadow-including ancestor
|
|
87
|
-
* of it, and the panel's shadow root is neither, it is a sibling. The
|
|
88
|
-
* relationship a reader actually needs is the panel's name, and the panel
|
|
89
|
-
* takes that as a string, which crosses anything. */
|
|
90
31
|
return html `<button
|
|
91
32
|
part="tab"
|
|
92
33
|
role="tab"
|
|
@@ -8,40 +8,9 @@ import { customElement, HelixElement, MixinDismissible } from '@fusion.dev/helix
|
|
|
8
8
|
import { x } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
11
|
-
/**
|
|
12
|
-
* @summary Labels content with a short piece of metadata.
|
|
13
|
-
*
|
|
14
|
-
* @description
|
|
15
|
-
* A tag is a label, and optionally a label the reader can take off.
|
|
16
|
-
* `dismissible` is what separates it from a badge: a badge reports a state the
|
|
17
|
-
* reader cannot change, a tag names something they can.
|
|
18
|
-
*
|
|
19
|
-
* Dismissing is a request. The tag does not remove itself, because the consumer
|
|
20
|
-
* owns the list it sits in and taking the node out from under them would put
|
|
21
|
-
* their state and the document out of step. Canceling `helix-dismiss` is a
|
|
22
|
-
* refusal.
|
|
23
|
-
*
|
|
24
|
-
* @since 0.1.0
|
|
25
|
-
* @status stable
|
|
26
|
-
* @category markers
|
|
27
|
-
* @aliases Chip, Pill, Label
|
|
28
|
-
* @documentation https://helix.fusion.dev/components/tag
|
|
29
|
-
*
|
|
30
|
-
* @slot - The tag's label.
|
|
31
|
-
* @slot prefix - A category the label belongs to, set before it and weightier.
|
|
32
|
-
*
|
|
33
|
-
* @event {HelixDismissEvent} helix-dismiss - Fired when the reader asks to remove the tag. Cancelable: refusing it takes the decision and nothing moves.
|
|
34
|
-
*
|
|
35
|
-
* @csspart dismiss - The control that asks to remove the tag.
|
|
36
|
-
* @csspart dismiss-mark - The glyph on the dismiss control, where none is slotted.
|
|
37
|
-
* @csspart prefix - The category before the label.
|
|
38
|
-
*/
|
|
39
11
|
let HelixHeadlessTag = class HelixHeadlessTag extends MixinDismissible(HelixElement) {
|
|
40
12
|
constructor() {
|
|
41
13
|
super(...arguments);
|
|
42
|
-
/* "Remove" rather than the mixin's "Dismiss". A tag is taken out of a set,
|
|
43
|
-
which is a different act from closing a message, and the control's name is
|
|
44
|
-
what a screen-reader user hears. */
|
|
45
14
|
this.dismissLabel = 'Remove';
|
|
46
15
|
}
|
|
47
16
|
render() {
|