@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.
Files changed (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. 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);
@@ -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() {