@excom/super-input 0.1.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 (41) hide show
  1. package/.rush/temp/chunked-rush-logs/super-input.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/super-input.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/super-input.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +9 -0
  14. package/config/rig.json +5 -0
  15. package/index.css +5 -0
  16. package/index.ts +17 -0
  17. package/package.json +44 -0
  18. package/rush-logs/super-input.apply-exports.cache.log +1 -0
  19. package/rush-logs/super-input.apply-exports.log +1 -0
  20. package/rush-logs/super-input.build_docs.cache.log +1 -0
  21. package/rush-logs/super-input.build_docs.log +1 -0
  22. package/rush-logs/super-input.build_package-metas.cache.log +1 -0
  23. package/rush-logs/super-input.build_package-metas.log +1 -0
  24. package/src/super-input.css +372 -0
  25. package/super-input.ts +165 -0
  26. package/support/custom-elements.json +327 -0
  27. package/support/demos/comprehensive.html +13 -0
  28. package/support/demos/invalid-message.html +7 -0
  29. package/support/demos/reflect-value.html +15 -0
  30. package/support/demos/slider.html +4 -0
  31. package/support/demos/with-format.html +4 -0
  32. package/support/dist-docs/super-input.md +208 -0
  33. package/support/docs/README.md +71 -0
  34. package/support/package-meta.json +275 -0
  35. package/support/tests/comprehensive.view.test.ts +22 -0
  36. package/support/tests/invalid-message.view.test.ts +21 -0
  37. package/support/tests/reflect-value.view.test.ts +24 -0
  38. package/support/tests/slider.view.test.ts +24 -0
  39. package/support/tests/super-input.test.ts +245 -0
  40. package/support/tests/with-format.view.test.ts +20 -0
  41. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 35.44262400000002
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 51.63553899999988
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 99.33473099999992
3
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "../../packages/super-input": "../../packages/super-input:fueVqvrObk+Av74S5aPlYPlhcaLS7CnIrVGb9WU6O8s=:",
3
+ "input-format@0.3.14": "input-format@0.3.14:wcZZfzrfa6WangE6QCn6dztFBYbHJDpmvmqRVDmgNCY=:",
4
+ "js-tokens@4.0.0": "js-tokens@4.0.0:bylIGj6At7FynPHmuZRgqLdhMSiaXEF+4m8SwJ0gY5k=:",
5
+ "loose-envify@1.4.0": "loose-envify@1.4.0:jl4j5x+ArQHmZCLrhcyKySMYfzxa2ijSEIuGDd0Pnn4=:",
6
+ "object-assign@4.1.1": "object-assign@4.1.1:gEYm2YMdR9pgGzUfDEsvSm8ByOtns3HUdx2PKjOtz8o=:",
7
+ "prop-types@15.8.1": "prop-types@15.8.1:F1eP/KKS5NQ0iflvTEPenWZltp31hhxQ7mL9wYzJELU=:",
8
+ "react-is@16.13.1": "react-is@16.13.1:qb0MRFVn3OE37GB60Jr8crveic53PdhMhnO/V/iWDPw=:"
9
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
package/index.css ADDED
@@ -0,0 +1,5 @@
1
+ @import "./src/super-input.css";
2
+
3
+ @layer {
4
+ @mixin module-super-input;
5
+ }
package/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { SuperInput } from "./super-input";
2
+
3
+ SuperInput.define();
4
+
5
+ export { SuperInput };
6
+
7
+ type T_HTMLSuperInputElement = typeof SuperInput.CustomElement;
8
+ declare global {
9
+ interface HTMLSuperInputElement extends T_HTMLSuperInputElement {}
10
+ interface Window {
11
+ HTMLSuperInputElement: HTMLSuperInputElement;
12
+ }
13
+ interface HTMLElementTagNameMap {
14
+ "super-input": HTMLSuperInputElement;
15
+ }
16
+ }
17
+ export type { HTMLSuperInputElement };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@excom/super-input",
3
+ "version": "0.1.0",
4
+ "description": "<super-input> custom element",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "input-format": "^0.3.10",
12
+ "@excom/neutron": "^0.1.0",
13
+ "@excom/valence": "^0.1.0"
14
+ },
15
+ "peerDependencies": {},
16
+ "devDependencies": {
17
+ "@excom/heft-rig": "^0.1.0"
18
+ },
19
+ "repository": {
20
+ "url": "excom-dev/nucleus",
21
+ "directory": "packages/super-input"
22
+ },
23
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/super-input/support/docs/README.md",
24
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
25
+ "keywords": [
26
+ "super-input",
27
+ "neutron",
28
+ "custom-elements"
29
+ ],
30
+ "excom": {
31
+ "packageType": "kit-element"
32
+ },
33
+ "scripts": {
34
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
35
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
36
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
37
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
38
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
39
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
40
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
41
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
42
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1,372 @@
1
+ @import "@excom/valence/src/util.default.css";
2
+
3
+ /**
4
+ * Styles for `<super-input>`.
5
+ * @element super-input
6
+ */
7
+
8
+ /* Aliases */
9
+ @custom-selector :--super-input super-input, .tag-super-input;
10
+ @custom-selector :--super-input--has-reflect-value
11
+ [reflect-value],
12
+ [data-reflect-value];
13
+ @custom-selector :--super-input--has-current-value
14
+ [current-value],
15
+ [data-current-value];
16
+
17
+ @define-mixin slider-track {
18
+ height: var(--super-input-track-height);
19
+ line-height: normal;
20
+ background: linear-gradient(
21
+ to right,
22
+ var(--super-input-accent) 0%,
23
+ var(--super-input-accent) var(--super-input-progress, 50%),
24
+ transparent var(--super-input-progress, 50%),
25
+ transparent 100%
26
+ );
27
+ }
28
+
29
+ @define-mixin slider-thumb {
30
+ margin: calc(
31
+ calc(var(--super-input-thumb-size) * -0.5) +
32
+ var(--super-input-track-height)
33
+ )
34
+ 0 0 0;
35
+ box-sizing: border-box;
36
+ cursor: grab;
37
+ width: var(--super-input-thumb-size);
38
+ height: var(--super-input-thumb-size);
39
+ background: var(--super-input-thumb-color);
40
+ border-radius: var(--super-input-thumb-radius, 100%);
41
+ border: var(
42
+ --super-input-thumb-border,
43
+ var(--super-input-border-width) solid var(--super-input-primary)
44
+ );
45
+ -webkit-appearance: none;
46
+ appearance: none;
47
+ }
48
+
49
+ @define-mixin module-super-input {
50
+ :root {
51
+ /**
52
+ * Foreground / label color.
53
+ * @cssproperty
54
+ * @syntax <color>
55
+ */
56
+ --super-input-color: var(--v-color, inherit);
57
+ /**
58
+ * Surface background behind the control (and raised-label mask).
59
+ * @cssproperty
60
+ * @syntax <color>
61
+ */
62
+ --super-input-bg: var(--v-form-element-background-color, transparent);
63
+ /**
64
+ * Default border color for raised inputs.
65
+ * @cssproperty
66
+ * @syntax <color>
67
+ */
68
+ --super-input-border-color: var(
69
+ --v-form-element-border-color,
70
+ currentColor
71
+ );
72
+ /**
73
+ * Focus ring / focus border color.
74
+ * @cssproperty
75
+ * @syntax <color>
76
+ */
77
+ --super-input-focus-color: var(
78
+ --v-form-element-focus-color,
79
+ var(--super-input-border-color)
80
+ );
81
+ /**
82
+ * Corner radius for raised inputs.
83
+ * @cssproperty
84
+ * @syntax <length>
85
+ */
86
+ --super-input-border-radius: var(--v-border-radius, 4px);
87
+ /**
88
+ * Border width for raised inputs.
89
+ * @cssproperty
90
+ * @syntax <length>
91
+ */
92
+ --super-input-border-width: var(--v-border-width, 1px);
93
+ /**
94
+ * Duration for border / label transitions.
95
+ * @cssproperty
96
+ * @syntax <time>
97
+ */
98
+ --super-input-transition-duration: var(--v-transition-duration-fast, 0.15s);
99
+ /**
100
+ * Easing for border / label transitions.
101
+ * @cssproperty
102
+ * @syntax *
103
+ */
104
+ --super-input-transition-ease: var(--v-transition-ease-out, ease-out);
105
+ /**
106
+ * Accent color (range fill, thumb border, focus chrome).
107
+ * @cssproperty
108
+ * @syntax <color>
109
+ */
110
+ --super-input-primary: var(--v-primary, blue);
111
+ /**
112
+ * Gap between a floating label and the control.
113
+ * @cssproperty
114
+ * @syntax <length>
115
+ */
116
+ --super-input-label-margin-bottom: calc(21px * 0.375);
117
+ /**
118
+ * Base font size for the control and value readout.
119
+ * @cssproperty
120
+ * @syntax <length>
121
+ */
122
+ --super-input-font-size: 16px;
123
+ /**
124
+ * Computed label line box height (used when a `<label>` is present).
125
+ * @cssproperty
126
+ * @syntax <length>
127
+ */
128
+ --super-input-label-height: calc(
129
+ var(--v-line-height, 1.5) * var(--super-input-font-size)
130
+ );
131
+ }
132
+
133
+ :--super-input {
134
+ &:has(input[type="range"]) {
135
+ /**
136
+ * Host height for range layout (thumb + value readout).
137
+ * @cssproperty
138
+ * @syntax <length>
139
+ */
140
+ --super-input-height: calc(var(--super-input-thumb-size) + 36px);
141
+ /**
142
+ * Host width for range layout.
143
+ * @cssproperty
144
+ * @syntax <length> | <percentage>
145
+ */
146
+ --super-input-width: 100%;
147
+ /**
148
+ * Range minimum. Prefer setting on the host (or via CSS): CSS cannot
149
+ * read the child input's `min` attribute from the parent.
150
+ * @cssproperty
151
+ * @syntax <number>
152
+ */
153
+ --super-input-min: 0;
154
+ /**
155
+ * Range maximum. Prefer setting on the host (or via CSS).
156
+ * @cssproperty
157
+ * @syntax <number>
158
+ */
159
+ --super-input-max: 100;
160
+ /**
161
+ * Current range value used for fill / readout positioning. With
162
+ * `reflect-value`, may be driven from `attr(current-value)` where
163
+ * typed `attr()` is supported.
164
+ * @cssproperty
165
+ * @syntax <number>
166
+ */
167
+ --super-input-value: 50;
168
+ /* START this will not work in safari or firefox yet */
169
+ --super-input-value: attr(current-value type(<number>));
170
+ /* END */
171
+ /** @internal */
172
+ --super-input-zero: calc(
173
+ var(--super-input-value) - var(--super-input-min, 0)
174
+ );
175
+ /** @internal */
176
+ --super-input-range: calc(
177
+ var(--super-input-max, 100) - var(--super-input-min, 0)
178
+ );
179
+ /** @internal */
180
+ --super-input-number-raw: calc(
181
+ calc(var(--super-input-zero) / var(--super-input-range)) *
182
+ calc(var(--super-input-width) - var(--super-input-thumb-size))
183
+ );
184
+ /** @internal Fill / readout progress position along the track. */
185
+ --super-input-progress: calc(
186
+ var(--super-input-number-raw) + calc(var(--super-input-thumb-size) / 2)
187
+ );
188
+ /**
189
+ * Diameter of the range thumb.
190
+ * @cssproperty
191
+ * @syntax <length>
192
+ */
193
+ --super-input-thumb-size: 44px;
194
+ /**
195
+ * Range thumb fill color.
196
+ * @cssproperty
197
+ * @syntax <color>
198
+ */
199
+ --super-input-thumb-color: var(
200
+ --v-range-thumb-color,
201
+ var(--super-input-bg)
202
+ );
203
+ /**
204
+ * Active track / accent color for the range fill.
205
+ * @cssproperty
206
+ * @syntax <color>
207
+ */
208
+ --super-input-accent: var(--super-input-primary, blue);
209
+
210
+ /**
211
+ * Height of the range track.
212
+ * @cssproperty
213
+ * @syntax <length>
214
+ */
215
+ --super-input-track-height: 2px;
216
+
217
+ /** @internal */
218
+ --super-input-value-content: attr(current-value);
219
+ /** @internal */
220
+ --super-input-value-bottom: var(--super-input-thumb-size);
221
+ /** @internal */
222
+ --super-input-value-transform: translate(-50%, 0);
223
+
224
+ display: block;
225
+ position: relative;
226
+ box-sizing: border-box;
227
+ font-size: var(--super-input-font-size);
228
+ /**
229
+ * Font weight for the range value readout.
230
+ * @cssproperty --super-input-font-weight
231
+ * @syntax <number> | <integer>
232
+ * @default 700
233
+ */
234
+ font-weight: var(--super-input-font-weight, 700);
235
+ height: var(--super-input-height, 100%);
236
+ width: var(--super-input-width, 100%);
237
+ &:has(label) {
238
+ padding-top: calc(
239
+ var(--super-input-label-height) + var(--super-input-height, 100%)
240
+ );
241
+ label {
242
+ position: absolute;
243
+ margin: 0;
244
+ top: 0;
245
+ left: 0;
246
+ }
247
+ }
248
+
249
+ input,
250
+ &::before {
251
+ position: absolute;
252
+ bottom: calc(var(--super-input-thumb-size) / 2);
253
+ }
254
+ &::before {
255
+ position: absolute;
256
+ display: block;
257
+ box-sizing: border-box;
258
+ text-align: center;
259
+ padding-bottom: var(--super-input-label-margin-bottom);
260
+ bottom: var(--super-input-value-bottom);
261
+ left: var(--super-input-progress);
262
+ content: var(--super-input-value-content);
263
+ transform: var(--super-input-value-transform);
264
+ }
265
+ input {
266
+ box-sizing: border-box;
267
+ margin: 0;
268
+ width: 100%;
269
+ background: var(--super-input-bg);
270
+ height: var(--super-input-track-height);
271
+ outline: none;
272
+ z-index: 2;
273
+ -webkit-appearance: none;
274
+ -moz-appearance: none;
275
+ appearance: none;
276
+ /* Don't combine webkit + moz selectors; identical blocks still break WebKit. */
277
+ &::-webkit-slider-runnable-track {
278
+ @mixin slider-track;
279
+ }
280
+ &::-moz-range-track {
281
+ @mixin slider-track;
282
+ }
283
+ &::-webkit-slider-thumb {
284
+ @mixin slider-thumb;
285
+ }
286
+ &::-moz-range-thumb {
287
+ @mixin slider-thumb;
288
+ }
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Raised / floating-label text field appearance. Apply as
294
+ * `class="raised"` on `<super-input>` wrapping a text-like `<input>`.
295
+ * @cssclass
296
+ */
297
+ &.raised {
298
+ display: block;
299
+ position: relative;
300
+ margin-top: 8px;
301
+
302
+ input {
303
+ @mixin util-nested-card;
304
+ font-size: 1rem;
305
+ padding: 0 10px 0 10px;
306
+ outline: none;
307
+ border-radius: var(--super-input-border-radius);
308
+ color: var(--super-input-color);
309
+ border: var(--super-input-border-width) solid
310
+ var(--super-input-border-color);
311
+ transition: border-color var(--super-input-transition-duration)
312
+ var(--super-input-transition-ease);
313
+ &:focus {
314
+ border-color: var(--super-input-focus-color) !important;
315
+ }
316
+ &::placeholder {
317
+ transition: color var(--super-input-transition-duration)
318
+ var(--super-input-transition-ease);
319
+ }
320
+ }
321
+ label {
322
+ position: absolute;
323
+ margin: 0;
324
+ font-size: 0.9rem;
325
+ line-height: var(--v-line-height, 16px);
326
+ top: -14px;
327
+ left: 10px;
328
+ background: transparent;
329
+ color: var(--super-input-color);
330
+ padding: 0 3px;
331
+ pointer-events: none;
332
+ z-index: 1;
333
+ transition:
334
+ top var(--super-input-transition-duration)
335
+ var(--super-input-transition-ease),
336
+ font-size var(--super-input-transition-duration)
337
+ var(--super-input-transition-ease);
338
+ &::after {
339
+ /* a line to hide input border behind label */
340
+ content: "";
341
+ position: absolute;
342
+ top: 12px;
343
+ left: 0;
344
+ width: 100%;
345
+ height: 4px;
346
+ z-index: -1;
347
+ background-color: var(--super-input-bg);
348
+ }
349
+ }
350
+
351
+ /* `[reflect-value]`: raised label even with no placeholder */
352
+ &:--super-input--has-reflect-value:not(:--super-input--has-current-value):not(
353
+ :has(input:focus)
354
+ ),
355
+ /* Else fall back to `:placeholder-shown` */
356
+ &:not(:--super-input--has-reflect-value):has(input:placeholder-shown):not(
357
+ :has(input:focus)
358
+ ) {
359
+ label {
360
+ font-size: 1rem;
361
+ top: 16px;
362
+ &::after {
363
+ content: none;
364
+ }
365
+ }
366
+ input::placeholder {
367
+ color: transparent;
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
package/super-input.ts ADDED
@@ -0,0 +1,165 @@
1
+ import { Neutron } from "@excom/neutron";
2
+ // TODO swap this library for something lighter.
3
+ import {
4
+ format as formatInput,
5
+ parse as parseInput,
6
+ parseDigit,
7
+ templateFormatter,
8
+ templateParser,
9
+ } from "input-format";
10
+
11
+ const formatValue = (
12
+ value: string,
13
+ selection: number,
14
+ textFormat: string,
15
+ parseFormat?: string
16
+ ) => {
17
+ const parser = templateParser(parseFormat ?? textFormat, parseDigit);
18
+ const formatter = templateFormatter(textFormat);
19
+ return formatInput(parseInput(value, -1, parser).value, selection, formatter)
20
+ .text;
21
+ };
22
+
23
+ /**
24
+ * Wrapper around a native `<input>` that adds live text formatting, auto-labeling, and
25
+ * a reflected value attribute. Does not replace the input — enhances it. Pair it with
26
+ * `<super-form>` for form-level submission handling.
27
+ *
28
+ * @descendant input - Required native `<input>` to enhance.
29
+ * @descendant ?label - Optional native `<label>`. When `auto-label` is set,
30
+ * its `for` attribute is linked to the input's `id`.
31
+ *
32
+ * @example Basic
33
+ * <super-input auto-label>
34
+ * <label>Name</label>
35
+ * <input />
36
+ * </super-input>
37
+ *
38
+ * @example With text formatting
39
+ * <super-input text-format="(xxx) xxx-xxxx" auto-label>
40
+ * <label>Phone</label>
41
+ * <input pattern="^\(\d{3}\)\s\d{3}-\d{4}" />
42
+ * </super-input>
43
+ */
44
+ export const SuperInput = Neutron({
45
+ tag: "super-input",
46
+ props: {
47
+ /**
48
+ * @option
49
+ * Pattern defining visible formatting. Use `x` for any digit and any
50
+ * other character as a literal. Examples: `(xxx) xxx-xxxx`, `xx/xx/xxxx`,
51
+ * `xxx-xx-xxxx`. When set, the input value is live-formatted on every
52
+ * keystroke.
53
+ */
54
+ textFormat: String,
55
+ /**
56
+ * @option
57
+ * If set, this message is installed via `setCustomValidity()` when the
58
+ * native `invalid` event fires, and cleared on the next `keydown`. Makes
59
+ * built-in HTML validation surface a custom message.
60
+ */
61
+ invalidMessage: String,
62
+ /**
63
+ * @option
64
+ * When set, the current input value is mirrored to the `current-value`
65
+ * attribute so CSS and selectors can respond to it. Off by default because
66
+ * reflecting every keystroke is not free.
67
+ */
68
+ reflectValue: Boolean,
69
+ /**
70
+ * @state
71
+ * Mirrors the wrapped `<input>`'s value when `reflect-value` is set.
72
+ * Read-only from the app's point of view — writing it does not change
73
+ * the input.
74
+ */
75
+ currentValue: String,
76
+ /**
77
+ * @option
78
+ * When set, generates or uses an existing `id` on the `<input>` and associates the `<label>`.
79
+ */
80
+ autoLabel: Boolean,
81
+ _inputElement: HTMLInputElement,
82
+ },
83
+ })
84
+ .defineMethods({
85
+ doFormat: ({ textFormat, _inputElement }, parseFormat?: string) => {
86
+ if ((!!textFormat || parseFormat) && _inputElement?.value) {
87
+ return {
88
+ _inputElement: {
89
+ value: formatValue(
90
+ _inputElement!.value,
91
+ _inputElement!.selectionEnd ?? 0,
92
+ textFormat ?? "",
93
+ parseFormat
94
+ ),
95
+ },
96
+ };
97
+ }
98
+ },
99
+ setCurrentValue: ({ reflectValue, _inputElement }) => ({
100
+ currentValue: reflectValue ? _inputElement?.value || null : null,
101
+ }),
102
+ handleInput: ({ reflectValue }) => [
103
+ {
104
+ doFormat: [],
105
+ },
106
+ reflectValue && {
107
+ setCurrentValue: [],
108
+ },
109
+ ],
110
+ handleInvalid: ({ invalidMessage }) =>
111
+ invalidMessage && {
112
+ _inputElement: {
113
+ setCustomValidity: [invalidMessage],
114
+ },
115
+ },
116
+ handleValid: () => ({
117
+ _inputElement: {
118
+ setCustomValidity: [""],
119
+ },
120
+ }),
121
+ })
122
+ .onConnected((el) => ({
123
+ _inputElement: el.querySelector("input"),
124
+ }))
125
+ // formatting
126
+ .onPropChanged(
127
+ ["_inputElement", "reflectValue", "textFormat"],
128
+ ({ _inputElement, reflectValue, textFormat, handleInput }, previous) => [
129
+ // Rare: reformat the input programmatically
130
+ { doFormat: [previous?.textFormat ?? undefined] },
131
+ { setCurrentValue: [] },
132
+ _inputElement && {
133
+ _inputElement: {
134
+ // Same handler so these tasks stay in order
135
+ toggleListeners: [
136
+ ["input", handleInput, reflectValue || !!textFormat],
137
+ ],
138
+ },
139
+ },
140
+ ]
141
+ )
142
+ .onPropChanged(
143
+ ["_inputElement", "invalidMessage"],
144
+ ({ _inputElement, invalidMessage, handleInvalid, handleValid }) =>
145
+ !!_inputElement && {
146
+ _inputElement: {
147
+ toggleListeners: [
148
+ ["invalid", handleInvalid, !!invalidMessage],
149
+ ["keydown", handleValid, !!invalidMessage],
150
+ ],
151
+ },
152
+ }
153
+ )
154
+ .onPropChanged(["_inputElement", "autoLabel"], (ce) => {
155
+ const { _inputElement, autoLabel } = ce;
156
+ const labelElement = ce.querySelector("label");
157
+ if (_inputElement && autoLabel && labelElement) {
158
+ const labelValue =
159
+ _inputElement.id ||
160
+ labelElement.htmlFor ||
161
+ "super-input-" + Math.random().toString(36).substring(2, 10);
162
+ _inputElement.id = labelValue;
163
+ labelElement.htmlFor = labelValue;
164
+ }
165
+ });