@fusion.dev/helix-headless 0.2.0 → 0.4.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,126 +7,18 @@ 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, state } from 'lit/decorators.js';
10
- /**
11
- * @summary A picture that says whether it arrived.
12
- *
13
- * @description
14
- * **An element rather than a recipe, because of the part CSS cannot do.**
15
- * Reserving the box is `aspect-ratio` and needs nothing from anybody; `Link` and
16
- * the megamenu row are recipes in `preview.css` for exactly that reason. What
17
- * cannot be written as a rule is *when the picture arrived*: a stylesheet has no
18
- * way to know, and no way to hear that it never will. So this owns three states
19
- * and the two events that move between them, and leaves the box to the
20
- * stylesheet.
21
- *
22
- * **The placeholder is a slot with nothing in it by default.** A default of
23
- * `Skeleton` would be right and is not written here: this tier renders no other
24
- * component, and reaching for one would make the headless package depend on the
25
- * styled one. The styled tier draws the resting placeholder instead, and a
26
- * consumer who wants a `Skeleton`, a blurred thumbnail or a solid color slots
27
- * it.
28
- *
29
- * **No source is a loading state, not a fourth one.** A picture whose `src` has
30
- * not arrived yet is the common case for anything drawn from data, and the
31
- * useful answer there is the same as for one still on the wire: stand something
32
- * in its place and keep the ground reserved. So an element with no `src` shows
33
- * its placeholder and waits, rather than resolving to an empty box that has to
34
- * be laid out twice.
35
- *
36
- * **`alt` is a property and not an afterthought.** An empty string is a real
37
- * answer, meaning the picture is decoration and a screen reader should skip it,
38
- * which is why the attribute is always written even when it is empty: omitting
39
- * it entirely makes a reader announce the file name instead.
40
- *
41
- * @since 0.1.0
42
- * @status experimental
43
- * @category media
44
- * @aliases Picture, Img, Figure
45
- * @documentation https://helix.fusion.dev/components/image
46
- *
47
- * @slot fallback - Shown instead of it, if it never does.
48
- * @slot placeholder - Shown until the picture arrives.
49
- *
50
- * @event {Event} error - Relayed for the same reason.
51
- * @event {Event} load - Relayed, because the platform's own does not escape.
52
- *
53
- * @csspart fallback - What stands in when it fails.
54
- * @csspart image - The picture itself.
55
- * @csspart placeholder - What stands in until it arrives.
56
- */
57
10
  let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
58
11
  constructor() {
59
12
  super(...arguments);
60
- /**
61
- * What it shows, for a reader who cannot see it.
62
- *
63
- * Empty is a real answer and means decoration. Omitting the attribute is not
64
- * the same thing: a reader with nothing to read announces the file name.
65
- */
66
13
  this.alt = '';
67
- /**
68
- * How the picture is fetched across origins, which decides whether a canvas
69
- * can read it.
70
- */
71
14
  this.crossOrigin = null;
72
- /**
73
- * Whether the browser may decode off the main thread.
74
- *
75
- * `async` is the resting value and right for a picture in a page. `sync` is
76
- * what a consumer reaches for when the picture is about to be composited with
77
- * something else and a tear would show.
78
- */
79
15
  this.decoding = 'async';
80
- /**
81
- * How urgently to fetch it, for the one picture that is the page.
82
- *
83
- * The hero of a page is usually its largest contentful paint, and the fix is
84
- * two attributes rather than one: `loading="eager"` stops it waiting for the
85
- * viewport, and this stops it queueing behind everything else the parser
86
- * found first. Left alone the browser decides, which is right for the other
87
- * ninety-nine pictures.
88
- */
89
16
  this.fetchPriority = 'auto';
90
- /**
91
- * How much of the referring URL to send with the request.
92
- */
93
17
  this.referrerPolicy = '';
94
- /**
95
- * How much of the line it will take, passed through to the picture.
96
- */
97
18
  this.sizes = '';
98
- /**
99
- * Where the picture is.
100
- */
101
19
  this.src = '';
102
- /**
103
- * The candidate widths, passed through to the picture.
104
- */
105
20
  this.srcset = '';
106
- /**
107
- * Whether the picture is on its way, here, or never coming.
108
- *
109
- * Read-only to a consumer: it is a report rather than a setting.
110
- *
111
- * **Mirrored to `data-status`, and not reflected as `status`.** It is element
112
- * state, so reflecting it puts the element's own bookkeeping into the markup
113
- * a consumer rendered: React reads the attribute appearing at upgrade as a
114
- * hydration mismatch, and a serialized `<helix-image status="loaded">` pasted
115
- * back declares that a picture has arrived which this element has never
116
- * fetched. The `data-` name is the library's answer to both, being a name a
117
- * re-parse cannot mistake for a declaration, and it is what `data-closed` on
118
- * `Toast.Item` already does. A picture arrives after somebody fetched it,
119
- * which puts this on the safe side of hydration: a fact that can be true on
120
- * the *first* update mirrors to a custom state instead, which is what
121
- * `:state(open)` and `:state(checked)` are.
122
- *
123
- * `loading` writes nothing, because it is where every image starts and the
124
- * resting state of a mirror is the attribute's absence.
125
- */
126
21
  this.status = 'loading';
127
- /**
128
- * Which `src` the current status belongs to, so a new one starts over.
129
- */
130
22
  this.answered = '';
131
23
  this.handleLoad = (event) => {
132
24
  this.announce('loaded', event);
@@ -135,28 +27,6 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
135
27
  this.announce('error', event);
136
28
  };
137
29
  }
138
- /**
139
- * Says so under the platform's own name, because the platform's cannot escape.
140
- *
141
- * `load` and `error` on an `<img>` neither bubble nor compose, so the ones
142
- * fired inside this shadow root reach nobody outside it. Re-emitting from the
143
- * host is what `relayNativeEvent` is for, and it is what `Avatar` already does
144
- * for the same pair on the same element.
145
- *
146
- * **Unprefixed, unlike every other event here.** The prefix exists to stop a
147
- * collision with a native name, and there is none to collide with: the native
148
- * events cannot reach a consumer at all.
149
- *
150
- * **Not bubbling, and that is not a small point.** A bubbling relay of `load`
151
- * would be the tempting one, letting a gallery count its pictures with one
152
- * listener, and it reaches nothing in React: `load` is in react-dom's
153
- * `nonDelegatedEvents`, so there is no root
154
- * listener to catch it, and React attaches a direct one for `<img>` and never
155
- * for a custom element. `bindReactHandler` binds exactly the non-bubbling
156
- * ones, so matching the native propagation is what makes `onLoad` work at
157
- * all. A gallery does here what it already does for `<img>`, which is listen
158
- * in the capture phase or listen per picture.
159
- */
160
30
  announce(status, event) {
161
31
  this.status = status;
162
32
  const name = status === 'loaded' ? 'load' : 'error';
@@ -164,29 +34,8 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
164
34
  this.relayNativeEvent(event);
165
35
  return;
166
36
  }
167
- /* No original to relay: `settle` catches a picture that was already
168
- complete when the listener attached, which is the cached case, and the
169
- platform fired its `load` before anyone was listening. The synthesized
170
- one carries the native propagation rather than defaults, so a consumer
171
- cannot tell the two paths apart. */
172
37
  this.dispatchEvent(new Event(name, { bubbles: false, cancelable: false, composed: false }));
173
38
  }
174
- /**
175
- * Catches the picture that was already there.
176
- *
177
- * A cached image can finish loading before this element has a listener on it,
178
- * and then `load` never fires and the placeholder stays up over a picture that
179
- * is sitting right behind it. `complete` is the platform's own answer to that
180
- * question, and `naturalWidth` separates a decoded picture from one that
181
- * carries no pixels, which `complete` reports as finished either way.
182
- *
183
- * **It can only ever conclude success.** A picture that has not been attempted
184
- * looks exactly like one that failed: `complete` is true and `naturalWidth` is
185
- * zero for both, and a DOM that never fetches anything reports that for every
186
- * image it has, so deciding `error` here calls every picture broken the moment
187
- * it renders. Failure has an event of its own and that event is reliable, so
188
- * this is only here to catch the success that happened too early to hear.
189
- */
190
39
  settle() {
191
40
  const image = this.renderRoot?.querySelector('img');
192
41
  if (this.src === '' || !image?.complete || image.naturalWidth === 0) {
@@ -196,9 +45,6 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
196
45
  }
197
46
  willUpdate(changed) {
198
47
  super.willUpdate(changed);
199
- /* A new picture is a new question, so the old answer goes. Without this a
200
- second `src` inherits the first one's `loaded` and never shows its own
201
- placeholder. */
202
48
  if (changed.has('src') && this.answered !== this.src) {
203
49
  this.answered = this.src;
204
50
  this.status = 'loading';
@@ -209,9 +55,6 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
209
55
  if (changed.has('src')) {
210
56
  this.settle();
211
57
  }
212
- /* The mirror, written here rather than reflected. `loading` is the resting
213
- state and writes nothing, so a picture on its way carries no attribute
214
- and one that has arrived or failed says which. */
215
58
  if (changed.has('status')) {
216
59
  if (this.status === 'loading') {
217
60
  this.removeAttribute('data-status');
@@ -225,35 +68,18 @@ let HelixHeadlessImage = class HelixHeadlessImage extends HelixElement {
225
68
  super.firstUpdated?.(changed);
226
69
  this.settle();
227
70
  }
228
- /**
229
- * The picture's own state, under the platform's own names.
230
- *
231
- * The `<img>` is inside this shadow root and `querySelector` does not cross a
232
- * shadow boundary, so a consumer asking how big the picture turned out to be,
233
- * or whether it is already there, has nowhere else to ask. `status` answers a
234
- * narrower question in this library's words and is no substitute for the
235
- * numbers.
236
- *
237
- * `naturalWidth` and `naturalHeight` are the case worth having: they are how
238
- * a consumer sizes a lightbox or works out an aspect ratio, and they exist
239
- * nowhere else on the page.
240
- */
241
71
  get image() {
242
72
  return this.renderRoot?.querySelector('img') ?? null;
243
73
  }
244
- /** Whether the picture has finished arriving, however it went. */
245
74
  get complete() {
246
75
  return this.image?.complete ?? false;
247
76
  }
248
- /** Which candidate the browser actually chose out of `srcset`. */
249
77
  get currentSrc() {
250
78
  return this.image?.currentSrc ?? '';
251
79
  }
252
- /** The picture's own height in pixels, and zero until it has arrived. */
253
80
  get naturalHeight() {
254
81
  return this.image?.naturalHeight ?? 0;
255
82
  }
256
- /** The picture's own width in pixels, and zero until it has arrived. */
257
83
  get naturalWidth() {
258
84
  return this.image?.naturalWidth ?? 0;
259
85
  }
package/dist/index.js CHANGED
@@ -1,13 +1,4 @@
1
1
  import './element-events.generated.js';
2
- /**
3
- * Everything core exports, forwarded whole.
4
- *
5
- * Not a named list. The generated React package imports the type each public
6
- * property is declared with, so anything a component newly reaches for (a
7
- * value-change event, a placement type, a close event) compiles here and breaks
8
- * there the moment the list misses it. Forwarding the module has nothing to
9
- * keep in step.
10
- */
11
2
  export * from '@fusion.dev/helix-core';
12
3
  export * from './accordion/index.js';
13
4
  export * from './announcement/index.js';
@@ -8,177 +8,27 @@ import { AriaForwardController, customElement, drawIcon, HelixFormElement, holdC
8
8
  import { eye, eyeSlash, x } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
- /**
12
- * @summary A single line of text a reader types into.
13
- *
14
- * @description
15
- * Renders a real `<input>`, so the caret, selection, autofill, spellcheck, the
16
- * password manager, and the on-screen keyboard a phone chooses all come from the
17
- * platform. `delegatesFocus` puts focus on it, and it serializes into the
18
- * server's markup, so the field is usable before any script loads.
19
- *
20
- * **The input inside a shadow root submits nothing**, because the form is on the
21
- * other side of the boundary. The host is form-associated instead and reports
22
- * its own value through `ElementInternals`, which is also what carries validity
23
- * and what answers a form reset.
24
- *
25
- * `value` and `defaultValue` are separate for the reason `checked` and
26
- * `defaultChecked` are: `value` is what is in the field now and
27
- * `defaultValue` is what the markup asked for, which is where a reset returns
28
- * to and is gone the moment anyone types.
29
- *
30
- * **Validity is the platform's.** The `type` and `required` are set on the real
31
- * input and its `validity` is copied onto the host, so an email field rejects
32
- * what the browser says is not an email rather than what a regular expression
33
- * here guesses. What this adds is the anchor, so `reportValidity` points the
34
- * bubble at the input rather than at the host.
35
- *
36
- * `pattern`, `minlength` and `maxlength` are forwarded with them.
37
- *
38
- * **`clearable` and `reveal` are the field's, not the consumer's.** Neither is
39
- * a slot's worth of variation: one empties the value, the other flips the type,
40
- * and both want the same position, the same size and the same disabled
41
- * behavior. The `end` slot stays for what actually varies, a unit or a prefix.
42
- *
43
- * Both opt in rather than following from `type`, and are booleans that default
44
- * to false. A Lit boolean property that defaults to *true* cannot be turned off
45
- * from HTML at all, only from script, which is the wrong way round for
46
- * revealing a password: a consumer should have to ask for it. Drawer's `modal`
47
- * defaults true for the opposite reason, that losing a focus trap by forgetting
48
- * a word is worse than having to say so.
49
- *
50
- * @since 0.1.0
51
- * @status experimental
52
- * @category forms
53
- * @aliases TextField, Field, TextInput
54
- * @documentation https://helix.fusion.dev/components/input
55
- *
56
- * @slot end - After the text, for an icon or a unit.
57
- * @slot start - Before the text, for an icon or a prefix.
58
- *
59
- * @event {Event} change - Relayed from the input once the reader has finished.
60
- * @event {HelixBeforeChangeEvent} helix-before-change - Fired as the text changes, and cancelable.
61
- * @event {Event} input - Announced once the value has moved.
62
- *
63
- * @csspart clear - The button that empties the field.
64
- * @csspart control - The input itself.
65
- * @csspart feedback - What is wrong with it.
66
- * @csspart field - The box holding everything.
67
- * @csspart hint - Help under the field, while nothing is wrong.
68
- * @csspart label - The question, over the field.
69
- * @csspart reveal - The button that shows and hides a password.
70
- */
71
11
  let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(MixinField(HelixFormElement)) {
72
12
  constructor() {
73
13
  super(...arguments);
74
- /* A consumer's `aria-describedby` names ids in their own tree and the control
75
- is in this shadow root, so the reference has to be carried down as an
76
- element rather than copied as an id. See `AriaForwardController`: measured
77
- in Chrome's own tree, the host held the description and the control the
78
- reader is actually on was exposed without it. */
79
14
  this.aria = new AriaForwardController(this, {
80
15
  target: () => this.renderRoot?.querySelector('[part="control"]') ?? null,
81
16
  });
82
- /** Typed or dragged, so every edit is announced. See `announcesInput`. */
83
17
  this.announcesInput = true;
84
- /**
85
- * What the browser may autofill here, as one of the platform's own tokens.
86
- *
87
- * Empty leaves the attribute off, which is the browser's default. Set it to
88
- * a token like `email` or `bday` to ask for autofill, or to `off` to refuse.
89
- */
90
18
  this.autocomplete = '';
91
- /**
92
- * Whether the field offers a button that empties it.
93
- *
94
- * Shown only while there is something to clear, because a control that does
95
- * nothing is worse than no control: a reader who presses it and sees no
96
- * change has to work out whether the field was already empty or the button
97
- * is broken.
98
- */
99
19
  this.clearable = false;
100
- /**
101
- * What the clear button is called, since an icon has no name of its own.
102
- */
103
20
  this.clearLabel = 'Clear';
104
- /**
105
- * A regular expression the value has to match, as the platform reads it.
106
- *
107
- * Forwarded rather than checked here, which is the whole reason validity is
108
- * the platform's: the browser knows what a `pattern` means, anchors it at both
109
- * ends, and produces the message in the reader's own language.
110
- */
111
21
  this.pattern = '';
112
- /**
113
- * Shown while the field is empty.
114
- *
115
- * Not a label: it goes when typing starts.
116
- */
117
22
  this.placeholder = '';
118
- /**
119
- * Whether the field refuses input while still submitting its value.
120
- *
121
- * **The attribute is `readonly`, which is the platform's spelling.** A
122
- * camelCase property needs an explicit `attribute`, and dash-casing a word
123
- * HTML writes as one costs what an attribute always costs here: nothing
124
- * warns. `<helix-input readonly>` is what anybody who has written HTML types;
125
- * against `read-only` it parses to no property at all, and the field stays
126
- * editable with no error anywhere. Measured on the dash-cased form, `readOnly`
127
- * came back `false`.
128
- *
129
- * A part was renamed for
130
- * the same kind of reason: there is no `helix-v` tag, so no consumer is
131
- * holding the old spelling, and this is free today and a deprecation after
132
- * the first release.
133
- */
134
23
  this.readOnly = false;
135
- /**
136
- * Whether the password is currently showing.
137
- *
138
- * State rather than a property: it is a reader's momentary choice about what
139
- * is on their own screen, it must not survive anything, and a consumer that
140
- * could set it could reveal a password without the reader asking.
141
- */
142
24
  this.revealed = false;
143
- /**
144
- * Every keystroke, cancelable, and put back where it is refused.
145
- *
146
- * The input has already taken the character by the time this runs, so a
147
- * consumer that refuses the change has to see the field returned to what the
148
- * element still holds. Otherwise the text on screen and the value the form
149
- * would submit disagree, which is the one thing a controlled field must never
150
- * do.
151
- */
152
25
  this.handleInput = (event) => {
153
- /* **Stopped, because the control inside this one is not a control a consumer
154
- has.** `input` is composed by specification, so the native event from the
155
- `<input>` in this shadow root crosses the boundary and reaches the host
156
- beside the one the mixin announces: measured in Chrome 148, one keystroke
157
- produced `helix-before-change`, this element's own `input`, and the inner
158
- control's, so `onInput` ran twice and one of the two carried the value
159
- from before the change. `handleChange` two methods down does the same. */
160
26
  event.stopPropagation();
161
27
  const control = event.target;
162
28
  const next = control.value;
163
- /* Through the mixin, which announces and then applies only if the value is
164
- the element's to move. The native control has already changed either way,
165
- so wherever the answer does not follow it has to be put back: that is a
166
- refusal, and it is equally a consumer who owns the value and has not
167
- given a new one yet. Both are "what is on screen is not the answer". */
168
29
  this.requestValue(next);
169
30
  restoreValue(control, this.value);
170
31
  };
171
- /**
172
- * Empties the field and gives the caret back.
173
- *
174
- * Focus returns to the input rather than staying on the button, which is
175
- * about to disappear: a reader who cleared a field is going to type in it,
176
- * and focus resting on a control that is being removed lands on the body.
177
- *
178
- * Through `requestValue` like every other change, so a consumer who owns the
179
- * value hears about it and decides, and a canceled clear leaves the text
180
- * where it was.
181
- */
182
32
  this.handleClear = () => {
183
33
  this.requestValue('');
184
34
  const control = this.control;
@@ -187,74 +37,24 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
187
37
  control.focus();
188
38
  }
189
39
  };
190
- /** Relayed, because `change` does not cross a shadow boundary on its own. */
191
40
  this.handleChange = (event) => {
192
41
  event.stopPropagation();
193
42
  this.dispatchEvent(new Event('change', { bubbles: true, composed: false }));
194
43
  };
195
- /**
196
- * Whether a password field offers a toggle that shows what was typed.
197
- *
198
- * Ignored on every other type. A reveal on an email field would be a button
199
- * that does nothing visible, and guarding it here rather than asking the
200
- * consumer not to means the property can be set once on a component that
201
- * changes type.
202
- */
203
44
  this.reveal = false;
204
- /**
205
- * What the reveal toggle is called.
206
- *
207
- * The state is `aria-pressed`, not this.
208
- */
209
45
  this.revealLabel = 'Show password';
210
- /**
211
- * What the field accepts, which decides the keyboard and the validation.
212
- *
213
- * **Not reflected.** Reflection exists so a selector can reach a value, and
214
- * no stylesheet in this repository selects on `type`. Reflecting it writes
215
- * `type="text"` onto every `<helix-input>` ever rendered, an attribute no
216
- * consumer's markup has, which is the fault the area rules write up under
217
- * `Button`'s `href`, `target` and `rel`.
218
- *
219
- * Nothing reads it from the attribute either. The real `<input>` inside takes
220
- * it as a property binding, which is what makes the keyboard and the
221
- * validation follow.
222
- */
223
46
  this.type = 'text';
224
47
  }
225
- /**
226
- * The type the real input is given, which is not always the type asked for.
227
- *
228
- * A revealed password is a text field for exactly as long as the reader is
229
- * looking at it. Reading through a getter rather than mutating `type` keeps
230
- * the property the consumer's: what they set is what they get back, whatever
231
- * the button is doing.
232
- */
233
48
  get effectiveType() {
234
49
  return this.revealed && this.type === 'password' ? 'text' : this.type;
235
50
  }
236
- /** Whether there is a clear button on screen right now. */
237
51
  get showsClear() {
238
52
  return this.clearable && this.value !== '' && !this.isDisabled && !this.readOnly;
239
53
  }
240
- /** Focus is delegated to the input, so the host is never the focus target. */
241
54
  static { this.shadowRootOptions = {
242
55
  ...HelixFormElement.shadowRootOptions,
243
56
  delegatesFocus: true,
244
57
  }; }
245
- /**
246
- * The caret and the selection, under the platform's own names.
247
- *
248
- * The `<input>` is inside this shadow root, so a consumer had no way to ask
249
- * where the selection is, let alone move it: `querySelector` does not cross
250
- * the boundary. An insert-at-cursor toolbar, a format button, a mask that
251
- * repositions the caret after rewriting the value, all of them need this and
252
- * none of them could be written. Textarea gained the same surface for the same
253
- * reason.
254
- *
255
- * Zero before there is a control, which is where an empty field's caret would
256
- * be anyway.
257
- */
258
58
  get selectionDirection() {
259
59
  return this.control?.selectionDirection ?? 'none';
260
60
  }
@@ -264,7 +64,6 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
264
64
  control.selectionDirection = direction;
265
65
  }
266
66
  }
267
- /** Where the selection ends, as an offset into the value. */
268
67
  get selectionEnd() {
269
68
  return this.control?.selectionEnd ?? 0;
270
69
  }
@@ -274,7 +73,6 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
274
73
  control.selectionEnd = at;
275
74
  }
276
75
  }
277
- /** Where the selection starts, as an offset into the value. */
278
76
  get selectionStart() {
279
77
  return this.control?.selectionStart ?? 0;
280
78
  }
@@ -284,51 +82,29 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
284
82
  control.selectionStart = at;
285
83
  }
286
84
  }
287
- /**
288
- * Selects a range, which is the one operation the three accessors above
289
- * cannot express between them.
290
- *
291
- * Setting `selectionStart` past the current `selectionEnd` drags the end
292
- * along, so the two writes a consumer would make in sequence do not compose
293
- * into the range they asked for. The platform has this method for that reason
294
- * and so does this.
295
- */
296
85
  setSelectionRange(start, end, direction) {
297
86
  this.control?.setSelectionRange(start, end, direction);
298
87
  }
299
88
  get control() {
300
89
  return this.renderRoot.querySelector('[part="control"]');
301
90
  }
302
- /** Reports the value and the validity the host stands behind. */
303
91
  sync() {
304
92
  this.internals.setFormValue(this.value);
305
93
  const control = this.control;
306
94
  if (!control) {
307
95
  return;
308
96
  }
309
- /* The input's own verdict, copied over rather than recomputed. It knows
310
- what an email is and what `pattern` means, and it knows it the same way
311
- the browser will when the form is submitted. The anchor is what this adds:
312
- without it the validity bubble points at the host, which is a box with no
313
- text in it. */
314
97
  this.internals.setValidity(control.validity, control.validationMessage, control);
315
- // Straight after, because validity moved without any property moving.
316
98
  this.refreshFeedback();
317
99
  }
318
- /** Puts the caret in the field, the way a label pointing at one would. */
319
100
  focus(options) {
320
101
  this.control?.focus(options);
321
102
  }
322
- /** Selects everything in the field. */
323
103
  select() {
324
104
  this.control?.select();
325
105
  }
326
106
  willUpdate(changed) {
327
107
  super.willUpdate(changed);
328
- /* Never before the first update, which is the one the server does: there is
329
- no render root to query there, and `querySelector` is not merely empty on
330
- Lit's element shim, it is undefined. There is also no caret to keep before
331
- anything has rendered. */
332
108
  if (!this.hasUpdated) {
333
109
  return;
334
110
  }
@@ -344,10 +120,6 @@ let HelixHeadlessInput = class HelixHeadlessInput extends MixinControllerValue(M
344
120
  this.sync();
345
121
  }
346
122
  render() {
347
- /* Label, field, and one line under it.
348
- The hint and the feedback share that line rather than stacking, so a
349
- field is the same height either way and nothing below it moves when the
350
- message changes. */
351
123
  return html `${this.renderLabel()}
352
124
  <div part="field">
353
125
  <slot name="start"></slot>