@fusion.dev/helix-headless 0.1.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,19 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, HelixSelectEvent, MixinControllerOpen, } from '@fusion.dev/helix-core';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { html } from 'lit/static-html.js';
10
- /* Side-effect imports, because this renders both in its own shadow root and a
11
- custom element registers on import. The paths are relative, so this registers
12
- the base pair and the styled file's identical imports register the styled
13
- pair; `tier` picks which names the template asks for. */
14
10
  import '../../dialog/index.js';
15
11
  import '../../listbox/index.js';
16
- /**
17
- * A hotkey, as the pieces a `keydown` can be compared against.
18
- *
19
- * `mod` is the platform's own: Command on a Mac and Control everywhere else,
20
- * which is what every application means by it and what a consumer would
21
- * otherwise have to detect and write twice.
22
- */
23
12
  const parse = (hotkey) => {
24
13
  const parts = hotkey.toLowerCase().split('+');
25
14
  return {
@@ -29,105 +18,14 @@ const parse = (hotkey) => {
29
18
  shift: parts.includes('shift'),
30
19
  };
31
20
  };
32
- /**
33
- * @summary A list of commands, opened from anywhere and filtered by typing.
34
- *
35
- * @description
36
- * **The list is `helix-listbox`, filtering and all.** Listbox already owns
37
- * narrowing, the empty state, the highlight over what matched,
38
- * `aria-activedescendant`, scrolling an option into view and the keyboard that
39
- * moves between them. Its own note says a command palette is that list again
40
- * with something different opening it, and this is that something: a dialog,
41
- * and a key that summons it.
42
- *
43
- * **The value is not kept.** A palette runs a command and forgets it: nothing
44
- * here is a form control and nothing is submitted, so what it reports is that
45
- * an option was chosen, once, with `helix-select`. A consumer that wants the
46
- * last command remembered is a consumer with somewhere better to keep it.
47
- *
48
- * **The hotkey is global and the element does not have to be open to hear it.**
49
- * The listener is on the document, because the point of a palette is that it
50
- * answers from wherever the reader is, including from inside another control's
51
- * text field. `mod` is Command on a Mac and Control elsewhere, resolved here so
52
- * a consumer writes one thing.
53
- *
54
- * The filter is emptied as the palette closes, for the reason Combobox and
55
- * Select both record: a control reopened still narrowed by the last visit's
56
- * search is one whose second use starts from the first one's leftovers, and a
57
- * palette is used far more often than either.
58
- *
59
- * @since 0.1.0
60
- * @status stable
61
- * @category navigation
62
- * @aliases Command palette, Quick open, Spotlight
63
- * @documentation https://helix.fusion.dev/components/command
64
- *
65
- * @slot - The commands, which are `helix-option` elements and their groups.
66
- * @slot footer - Under the list, for the keys a reader can press.
67
- *
68
- * @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes. Cancelable: refusing it takes the decision and nothing moves.
69
- * @event {HelixBeforeOpenEvent} helix-before-open - Fired before the palette opens. Cancelable: refusing it takes the decision and nothing moves.
70
- * @event {HelixSelectEvent} helix-select - Fired when a command is chosen. Cancelable: refusing it takes the decision and nothing moves.
71
- * @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
72
- *
73
- * @csspart dialog - The overlay's own box, forwarded from the dialog.
74
- * @csspart footer - The row under the list.
75
- * @csspart listbox - The list inside it.
76
- * @csspart listbox-empty - Shown when a filter has hidden everything.
77
- * @csspart listbox-feedback - What is wrong with it.
78
- * @csspart listbox-filter-icon - The mark beside that field.
79
- * @csspart listbox-filter-mark - The glyph beside the filter field, where none is slotted.
80
- * @csspart listbox-hint - Help under the list.
81
- * @csspart listbox-items - The list itself.
82
- * @csspart listbox-label - The question, over the list.
83
- * @csspart listbox-loading - Shown while the options are still on their way.
84
- * @csspart listbox-search - The region holding the field that narrows the list.
85
- * @csspart listbox-search-field - The field itself.
86
- */
87
21
  let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpen(HelixElement) {
88
22
  constructor() {
89
23
  super(...arguments);
90
- /**
91
- * Words shown where the filter has matched nothing.
92
- */
93
24
  this.emptyText = 'No commands';
94
- /**
95
- * The key that opens it, as `mod+k` or `shift+p` or plain `k`.
96
- *
97
- * Empty turns it off, for a consumer who opens the palette from a button or
98
- * from their own shortcut handler. There is no list of defaults to fight
99
- * with: one key, said once.
100
- */
101
25
  this.hotkey = 'mod+k';
102
- /**
103
- * Accessible name for the overlay, which has no visible heading.
104
- */
105
26
  this.label = 'Commands';
106
- /**
107
- * Shown in the field before anything is typed.
108
- */
109
27
  this.placeholder = 'Search commands';
110
- /**
111
- * Whether the consumer put anything under the list.
112
- *
113
- * Asked of the slot rather than of CSS. A wrapper holding a slot always has a
114
- * child element, which is the slot, so `:not(:has(*))` on it is never true
115
- * and an empty footer would draw its rule and its padding over nothing. Steps
116
- * was caught by the same thing.
117
- */
118
28
  this.footed = false;
119
- /**
120
- * Answers the hotkey from wherever the reader is.
121
- *
122
- * On the document rather than on this element, which is never the thing with
123
- * focus: a palette that only answered when it already had focus would be a
124
- * palette nobody could open.
125
- *
126
- * `preventDefault` because every key worth using is already the browser's.
127
- * Command-K focuses the address bar in some browsers and opens a search in
128
- * others, and a palette that opened underneath one of those would be a
129
- * palette the reader never sees.
130
- */
131
29
  this.handleHotkey = (event) => {
132
30
  if (this.hotkey === '') {
133
31
  return;
@@ -143,14 +41,6 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
143
41
  event.preventDefault();
144
42
  this.requestOpen(!this.open);
145
43
  };
146
- /**
147
- * Takes what the list announced and says it under this element's name.
148
- *
149
- * The listbox says `helix-before-change`, which is the right word for a control that
150
- * holds a value and the wrong one for a palette: nothing here changed, a
151
- * command ran. It is stopped and re-announced rather than relayed, so a
152
- * consumer hears one event naming the element they put on the page.
153
- */
154
44
  this.handleChange = (event) => {
155
45
  event.stopPropagation();
156
46
  const { value } = event.detail;
@@ -159,26 +49,6 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
159
49
  }
160
50
  this.requestOpen(false);
161
51
  };
162
- /**
163
- * Takes the row's second destination, where the reader asked for it.
164
- *
165
- * **Command intercepts rather than Listbox reporting, and that is the whole
166
- * design.** Enter belongs to the listbox: it owns the active row, it decides
167
- * what choosing means, and it announces `helix-before-change`. Teaching it about a
168
- * modifier would put a palette's idea of a second destination inside a
169
- * control that is also a select and a combobox, neither of which has one.
170
- *
171
- * So this listens in the capture phase, before the listbox sees the key, and
172
- * only for the one combination that means something here. Anything else falls
173
- * straight through and the listbox behaves exactly as it always did.
174
- *
175
- * `mod` is Command on a Mac and Control elsewhere, which is what `hotkey`
176
- * already means by it two methods up.
177
- *
178
- * The pointer takes the same path for the same reason: a modifier-click on a
179
- * row is the same intent as a modifier-Enter on it, and both have to be
180
- * stopped before the row announces its ordinary selection.
181
- */
182
52
  this.handleAlternate = (event) => {
183
53
  if (!(event.metaKey || event.ctrlKey) || !this.open) {
184
54
  return;
@@ -199,21 +69,6 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
199
69
  this.requestOpen(false);
200
70
  }
201
71
  };
202
- /*
203
- * Keeps an option's own announcement inside the palette.
204
- *
205
- * Every option says `helix-select` when it is chosen, which is how the list
206
- * hears it, and the options are light DOM children of this element, so it
207
- * carries straight on out to the consumer: two selections for one command
208
- * under one event name, told apart only by the target. The one this element
209
- * dispatches has itself as the target and is let past.
210
- *
211
- * `stopImmediatePropagation`, not `stopPropagation`. The consumer's listener
212
- * is on this same element, and stopping propagation only keeps an event off
213
- * the *next* node, so every other listener already bound here still runs. The
214
- * listbox hears it well before this, from inside the shadow root, so nothing
215
- * this element needs is lost by ending it here.
216
- */
217
72
  this.swallowOptionSelect = (event) => {
218
73
  if (event.target !== this) {
219
74
  event.stopImmediatePropagation();
@@ -223,21 +78,12 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
223
78
  get listbox() {
224
79
  return this.renderRoot?.querySelector('[part="listbox"]') ?? null;
225
80
  }
226
- /**
227
- * The row under the keyboard, read from the attribute the listbox writes.
228
- *
229
- * `data-active` rather than a reference the listbox hands over, because the
230
- * options are this element's own light DOM children and the attribute is
231
- * already published: asking the listbox would mean a second channel saying
232
- * what the markup says.
233
- */
234
81
  get activeOption() {
235
82
  return this.querySelector('[data-active]');
236
83
  }
237
84
  connectedCallback() {
238
85
  super.connectedCallback();
239
86
  this.addEventListener('helix-select', this.swallowOptionSelect);
240
- /* Capture, so both reach this before the listbox and the row do. */
241
87
  this.addEventListener('keydown', this.handleAlternate, { capture: true });
242
88
  this.addEventListener('click', this.handleAlternate, { capture: true });
243
89
  document.addEventListener('keydown', this.handleHotkey);
@@ -251,18 +97,10 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
251
97
  }
252
98
  updated(changed) {
253
99
  super.updated?.(changed);
254
- /* On the change rather than on every update. Both of these reach into the
255
- listbox, and asking it to filter on a render it did not cause is how a
256
- render becomes a loop: the list updates, something it owns comes back
257
- through this element, and the next update asks again. Only the moment the
258
- palette opens or shuts is either of them true. */
259
100
  if (!changed.has('open')) {
260
101
  return;
261
102
  }
262
103
  if (this.open) {
263
- /* The field takes focus as the palette arrives. A reader who pressed a
264
- key to summon a search expects to be typing in it, and the dialog would
265
- otherwise put focus on itself. */
266
104
  this.renderRoot
267
105
  .querySelector('[part="listbox"]')
268
106
  ?.renderRoot?.querySelector('[part="search-field"]')
@@ -272,16 +110,6 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
272
110
  this.listbox?.requestFilter('');
273
111
  }
274
112
  }
275
- /**
276
- * The surface the palette opens in, behind a seam the styled tier can move.
277
- *
278
- * A method rather than a fragment inline, so a tier above can render its own,
279
- * wrap this one, pass it a property the base does not know about, or leave it
280
- * out, without reimplementing the whole template to do any of them. The tag is
281
- * interpolated rather than written, so `tier` resolves it: this renders the
282
- * headless element, and a styled subclass that sets `tier` renders the styled one
283
- * from the same template.
284
- */
285
113
  renderDialog() {
286
114
  return html `<${this.tag('dialog')}
287
115
  exportparts="dialog"
@@ -320,11 +148,6 @@ let HelixHeadlessCommand = class HelixHeadlessCommand extends MixinControllerOpe
320
148
  </${this.tag('dialog')}>`;
321
149
  }
322
150
  render() {
323
- /* No part of its own on the host, and the dialog's own box forwarded under
324
- its own name: `::part(dialog)` then reaches the thing with the shadow and
325
- the padding, rather than a host that is only a wrapper. `align` is not set
326
- here either, being a styled-tier property: where the panel sits is
327
- presentation, and this tier renders no CSS. */
328
151
  return html `${this.renderDialog()}`;
329
152
  }
330
153
  };
@@ -13,97 +13,13 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
13
13
  import { html } from 'lit/static-html.js';
14
14
  import { HelixHeadlessIconButton } from '../../icon-button/root/icon-button-root.js';
15
15
  import '../../tooltip/index.js';
16
- /** How long the confirmation stands before the button goes back to itself. */
17
16
  const CONFIRM = 1600;
18
- /**
19
- * @summary A button that puts a string on the clipboard and says it did.
20
- *
21
- * @description
22
- * **The confirmation is the component.** Writing to the clipboard is three
23
- * lines; what makes this worth an element is that the platform gives a reader
24
- * no feedback at all. A press that copies and looks identical to a press that
25
- * failed is a control nobody trusts, so they press it again, and the second
26
- * press is indistinguishable from the first.
27
- *
28
- * **Announced after the write, never before.** A clipboard write is a
29
- * permission the browser may refuse, and it is refused for real reasons: an
30
- * insecure origin, a document that is not focused, a user agent that requires a
31
- * gesture this press did not qualify as. So the confirmation and the event both
32
- * wait for the promise, and a refusal leaves the button exactly as it was.
33
- * `HelixCopyEvent` is not cancelable for the same reason: by the time there is
34
- * anything to say, it has already happened.
35
- *
36
- * **An icon button, extending IconButton.** Both of this button's faces are
37
- * icons and it never carries a word, so it is the square control rather than a
38
- * text button that happens to hold a glyph: it inherits the square box, and
39
- * with it the rule that an icon-only control must be named. `label` is not
40
- * optional here for the same reason it is not optional there, and the two
41
- * faces make it sharper: the accessible name has to describe the press, not
42
- * whichever picture is showing when a reader arrives.
43
- *
44
- * `data-copied`, not a reflected `copied`. State never reflects here: a
45
- * serialized `copied` attribute would come back from a paste as a button
46
- * claiming to have copied something it never held.
47
- *
48
- * @since 0.1.0
49
- * @status experimental
50
- * @category actions
51
- * @aliases Copy, Copy to clipboard
52
- * @documentation https://helix.fusion.dev/components/copy-button
53
- *
54
- * @slot - The resting icon. Falls back to a clipboard.
55
- * @slot copied - What replaces it once the write succeeds. Falls back to a check.
56
- *
57
- * @event {HelixCopyEvent} helix-copy - Fired after the clipboard write succeeds.
58
- *
59
- * @csspart button - The inner button.
60
- * @csspart mark - The clipboard glyph, and the confirmation that replaces it.
61
- * @csspart tooltip - The bubble, forwarded from the tooltip inside.
62
- * @csspart tooltip-arrow - The point, resting against the trigger.
63
- */
64
17
  let HelixHeadlessCopyButton = HelixHeadlessCopyButton_1 = class HelixHeadlessCopyButton extends HelixHeadlessIconButton {
65
18
  constructor() {
66
19
  super(...arguments);
67
- /**
68
- * What it is called, and says, after one.
69
- *
70
- * `attribute` written out, because Lit lowercases a property name rather than
71
- * dash-casing it, and `copiedlabel` is not what anyone would type.
72
- */
73
20
  this.copiedLabel = 'Copied';
74
- /**
75
- * What it is called, and says, before a press.
76
- *
77
- * Defaulted rather than left empty. An icon-only control must be named, and
78
- * the name of this one is not a judgment call: every copy button in the
79
- * world is called Copy. A consumer with a better word still sets it.
80
- */
81
21
  this.label = 'Copy';
82
- /**
83
- * What goes on the clipboard.
84
- *
85
- * **Narrows `Button.value`, which is the submit value, and the two agree.**
86
- * A copy button is `type="button"` and never submits, so the inherited
87
- * meaning has nothing to act on; where a consumer changes the type, what it
88
- * submits is what it copies, which is the reading anybody would expect of one
89
- * property called `value`. Declared here rather than inherited because the
90
- * clipboard is what this component is about and its own page should say so.
91
- */
92
22
  this.value = '';
93
- /**
94
- * The write, the confirmation, the timer and the event, all of them shared.
95
- *
96
- * **Never reach for `navigator.clipboard.writeText` directly.** It is inert in
97
- * a sandboxed iframe without `allow-same-origin`, which is the sandbox every
98
- * example on the docs site runs in, and guarding on the API existing does not
99
- * help there: the object exists and the write rejects. A copy button that does
100
- * nothing is indistinguishable from a broken one.
101
- *
102
- * The controller carries the `execCommand` fallback, checks the host is still
103
- * connected after the await (writing is asynchronous, so `hostDisconnected`
104
- * can run before there is a timer to clear), and registers itself so the timer
105
- * is released without an override here.
106
- */
107
23
  this.clipboard = new CopyController(this, { after: CONFIRM });
108
24
  this.handleCopy = async () => {
109
25
  if (this.isDisabled) {
@@ -112,32 +28,12 @@ let HelixHeadlessCopyButton = HelixHeadlessCopyButton_1 = class HelixHeadlessCop
112
28
  await this.clipboard.copy(this.value);
113
29
  };
114
30
  }
115
- /**
116
- * The words for the state it is in, used twice on purpose.
117
- *
118
- * The tooltip is the visible confirmation and the accessible name is the
119
- * spoken one, and they have to be the same words. A tooltip that changes to
120
- * Copied while the accessible name stays Copy tells a sighted reader it
121
- * worked and leaves a screen reader user pressing a button that never
122
- * acknowledges anything. Changing the name of the focused element is what
123
- * makes the announcement happen at all.
124
- */
125
31
  get says() {
126
32
  return this.copied ? this.copiedLabel : this.label;
127
33
  }
128
34
  get copied() {
129
35
  return this.clipboard.copied;
130
36
  }
131
- /**
132
- * Both faces, drawn rather than demanded.
133
- *
134
- * Slot fallback, which is this library's answer for content a component has a
135
- * sensible default for: a copy button with no icons is not a copy button, and
136
- * making every consumer slot the same clipboard is asking them to supply the
137
- * one thing the component is sure about. Slotting anything replaces it, at
138
- * assignment time, with no property to set and nothing for the server and the
139
- * client to disagree about.
140
- */
141
37
  static icon(glyph) {
142
38
  return html `<svg part="mark" fill="currentColor" viewBox=${glyph.viewBox} aria-hidden="true">
143
39
  ${unsafeSVG(glyph.content)}
@@ -155,45 +51,15 @@ let HelixHeadlessCopyButton = HelixHeadlessCopyButton_1 = class HelixHeadlessCop
155
51
  ${this.renderCopiedIcon()} ${this.renderCopyIcon()}
156
52
  </button>`);
157
53
  }
158
- /**
159
- * The mark for the resting state, behind a seam a tier can move.
160
- *
161
- * Two seams and not one taking the state, because the two marks are two
162
- * decisions: a tier changing the clipboard for a house glyph has no reason to
163
- * touch the confirmation, and the stylesheet already treats them separately,
164
- * the check holding its green through a hover where this one does not.
165
- */
166
54
  renderCopyIcon() {
167
55
  return html `<slot ?hidden=${this.copied}>${HelixHeadlessCopyButton_1.icon(copy)}</slot>`;
168
56
  }
169
- /**
170
- * The mark for the moment after a press, behind a seam of its own.
171
- *
172
- * Both slots render and CSS shows one, which is why this is a slot rather
173
- * than a branch: swapping which slot exists would unassign the consumer's
174
- * content on every press, and an element that leaves and re-enters the tree
175
- * loses focus with it, so the button a reader just pressed would stop being
176
- * the focused element mid-press.
177
- */
178
57
  renderCopiedIcon() {
179
58
  return html `<slot name="copied" ?hidden=${!this.copied}
180
59
  >${HelixHeadlessCopyButton_1.icon(check)}</slot
181
60
  >`;
182
61
  }
183
- /**
184
- * The bubble the control sits inside, behind a seam.
185
- *
186
- * It takes the control rather than rendering it, because this one wraps: a
187
- * tier above overriding it can drop the tooltip entirely and return the
188
- * button, put something else around it, or keep the tooltip and change only
189
- * how it is anchored, and every one of those is a normal thing to want.
190
- * Rendering the button here as well would make all three a rewrite.
191
- */
192
62
  renderTooltip(control) {
193
- /* `persist`, because this bubble is the confirmation rather than a hint
194
- about it. Without it the tooltip hid on the same click that swapped its
195
- content to Copied, so the words this component takes such care over were
196
- written and then taken off screen in the same frame. */
197
63
  return html `<${this.tag('tooltip')} content=${this.says} persist
198
64
  exportparts="arrow: tooltip-arrow, tooltip">
199
65
  ${control}