@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,275 @@
1
+ {
2
+ "shortName": "super-input",
3
+ "package": {
4
+ "name": "@excom/super-input",
5
+ "version": "0.1.0",
6
+ "description": "<super-input> custom element",
7
+ "peerDependencies": {},
8
+ "excom": {
9
+ "packageType": "kit-element"
10
+ }
11
+ },
12
+ "demos": {
13
+ "comprehensive": "<form>\n <super-input\n text-format=\"(xxx) xxx-xxxx\"\n invalid-message=\"That doesn't look like a US phone number\"\n auto-label\n class=\"raised\"\n >\n <label>Phone number</label>\n <input required pattern=\"^\\(\\d{3}\\)\\s\\d{3}-\\d{4}\" placeholder=\"(123) 456-7890\"/>\n </super-input>\n <!-- Form and button exist purely for the demo -->\n <button type=\"submit\">Submit</button>\n</form>\n",
14
+ "invalid-message": "<form>\n <super-input text-format=\"(xxx) xxx-xxxx\" invalid-message=\"That doesn't look like a US phone number\">\n <label>Phone number</label>\n <input required pattern=\"^\\(\\d{3}\\)\\s\\d{3}-\\d{4}\" />\n </super-input>\n <button type=\"submit\">Submit</button>\n</form>",
15
+ "reflect-value": "<super-input reflect-value class=\"raised\" id=\"si-rv\">\n <label>Email</label>\n <input type=\"email\" />\n <style>\n super-input#si-rv {\n &::after { color: orange; }\n &:not([current-value*=\".\"])::after {\n content: 'Email is missing \".\" character.';\n }\n &:not([current-value*=\"@\"])::after {\n content: 'Email is missing \"@\" character.';\n }\n }\n </style>\n</super-input>",
16
+ "slider": "<super-input reflect-value style=\"--super-input-min: 18; --super-input-max: 85;\">\n <label>Age</label>\n <input type=\"range\" name=\"age\" step=\"1\" value=\"36\" min=\"18\" max=\"85\">\n</super-input>",
17
+ "with-format": "<super-input text-format=\"(xxx) xxx-xxxx\">\n <label>Phone number</label>\n <input value=\"8902340170\" />\n</super-input>\n"
18
+ },
19
+ "readme": "<h1 id=\"md-super-input\">super-input</h1>\n<p>A lightweight element that wraps and upgrades the native <code>&lt;input&gt;</code> element.</p>\n<p><include-content data-demo=\"comprehensive\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Live text formatting</strong> e.g. phone numbers, dates, SSNs</li>\n<li><strong>Custom, native validity messages</strong> uses the browser&#39;s built-in validation UI to show your message</li>\n<li><strong>Auto-labeling</strong> it stitches a sibling <code>&lt;label&gt;</code> to the <code>&lt;input&gt;</code> via <code>id</code>/<code>for</code></li>\n<li><strong>Progressively enhanced</strong> Does <strong>not</strong> replace the native input; it enhances it. Everything you know\nabout <code>&lt;input&gt;</code> still applies.</li>\n<li><strong>Range slider</strong> ships with upgraded slider styling and functionality</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>Wrap a native <code>&lt;input&gt;</code> and optionally a <code>&lt;label&gt;</code>. Nothing else is required. </p>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-formatting-input-as-the-user-types\">Formatting input as the user types</h4>\n<p>Set <code>text-format</code> to a template using <code>x</code> as a character placeholder.</p>\n<p><include-content data-demo=\"with-format\"></include-content></p>\n<p>Common templates:</p>\n<ul>\n<li>Phone (US): <code>(xxx) xxx-xxxx</code></li>\n<li>Date: <code>xx/xx/xxxx</code></li>\n<li>SSN: <code>xxx-xx-xxxx</code></li>\n</ul>\n<p>The wrapped <code>&lt;input&gt;</code> sees the formatted value. Pair with <code>pattern</code> for\nvalidation.</p>\n<h4 id=\"md-custom-validity-messages\">Custom validity messages</h4>\n<p>Set <code>invalid-message</code> and the browser&#39;s native validation UI will surface it\nwhen the input fails. Use the native <code>pattern</code>, <code>required</code>, <code>min</code>, <code>max</code>, etc for validation.\nTry submitting the form in the demo below with an invalid phone number.</p>\n<p><include-content data-demo=\"invalid-message\"></include-content></p>\n<p>The message is installed via <code>setCustomValidity()</code> on the <code>invalid</code> event and\ncleared on the next <code>keydown</code>, so the input stops being marked invalid as\nsoon as the user tries again.</p>\n<h4 id=\"md-using-reflect-value\">Using reflect-value</h4>\n<p>The <code>reflect-value</code> attribute has two primary uses:</p>\n<ul>\n<li>Is required for animating <code>raised</code> labels without an input <code>placeholder</code> attribute</li>\n<li>Allows you to hook into it to run your own behaviors</li>\n</ul>\n<p>Here&#39;s an example with a raising label that will display an error if the user has not entered an email with an <code>@</code> or <code>.</code> characters.</p>\n<p><include-content data-demo=\"reflect-value\"></include-content></p>\n<h4 id=\"md-range-slider\">Range slider</h4>\n<p><code>&lt;super-input&gt;</code> ships with advanced slider CSS. Add <code>[type=&quot;range&quot;]</code> to the <code>input</code>. <code>[reflect-value]</code> is required for this to work natively in Chromium browsers. Safari and Firefox will need some help via Quark or JS until they support <a href=\"https://caniuse.com/mdn-css_types_type\">the CSS <code>type()</code> function</a>. You must also set the min/max CSS variables to match the min/max on the input.</p>\n<p>Be cautious using this feature, as it aesthetically relies on non-standard pseudo elements for the time being.</p>\n<p><include-content data-demo=\"slider\"></include-content></p>\n",
20
+ "docs": {
21
+ "readme": "<h1 id=\"md-super-input\">super-input</h1>\n<p>A lightweight element that wraps and upgrades the native <code>&lt;input&gt;</code> element.</p>\n<p><include-content data-demo=\"comprehensive\"></include-content></p>\n<h2 id=\"md-features\">Features</h2>\n<ul>\n<li><strong>Live text formatting</strong> e.g. phone numbers, dates, SSNs</li>\n<li><strong>Custom, native validity messages</strong> uses the browser&#39;s built-in validation UI to show your message</li>\n<li><strong>Auto-labeling</strong> it stitches a sibling <code>&lt;label&gt;</code> to the <code>&lt;input&gt;</code> via <code>id</code>/<code>for</code></li>\n<li><strong>Progressively enhanced</strong> Does <strong>not</strong> replace the native input; it enhances it. Everything you know\nabout <code>&lt;input&gt;</code> still applies.</li>\n<li><strong>Range slider</strong> ships with upgraded slider styling and functionality</li>\n</ul>\n<h2 id=\"md-installation\">Installation</h2>\n<p><include-content is-active template-ref=\"/views/install-section/install-section.html\"></include-content></p>\n<h2 id=\"md-usage\">Usage</h2>\n<p>Wrap a native <code>&lt;input&gt;</code> and optionally a <code>&lt;label&gt;</code>. Nothing else is required. </p>\n<h3 id=\"md-api-reference\">API Reference</h3>\n<p><include-content is-active template-ref=\"/views/api-reference/api-reference.html\"></include-content></p>\n<h3 id=\"md-examples\">Examples</h3>\n<h4 id=\"md-formatting-input-as-the-user-types\">Formatting input as the user types</h4>\n<p>Set <code>text-format</code> to a template using <code>x</code> as a character placeholder.</p>\n<p><include-content data-demo=\"with-format\"></include-content></p>\n<p>Common templates:</p>\n<ul>\n<li>Phone (US): <code>(xxx) xxx-xxxx</code></li>\n<li>Date: <code>xx/xx/xxxx</code></li>\n<li>SSN: <code>xxx-xx-xxxx</code></li>\n</ul>\n<p>The wrapped <code>&lt;input&gt;</code> sees the formatted value. Pair with <code>pattern</code> for\nvalidation.</p>\n<h4 id=\"md-custom-validity-messages\">Custom validity messages</h4>\n<p>Set <code>invalid-message</code> and the browser&#39;s native validation UI will surface it\nwhen the input fails. Use the native <code>pattern</code>, <code>required</code>, <code>min</code>, <code>max</code>, etc for validation.\nTry submitting the form in the demo below with an invalid phone number.</p>\n<p><include-content data-demo=\"invalid-message\"></include-content></p>\n<p>The message is installed via <code>setCustomValidity()</code> on the <code>invalid</code> event and\ncleared on the next <code>keydown</code>, so the input stops being marked invalid as\nsoon as the user tries again.</p>\n<h4 id=\"md-using-reflect-value\">Using reflect-value</h4>\n<p>The <code>reflect-value</code> attribute has two primary uses:</p>\n<ul>\n<li>Is required for animating <code>raised</code> labels without an input <code>placeholder</code> attribute</li>\n<li>Allows you to hook into it to run your own behaviors</li>\n</ul>\n<p>Here&#39;s an example with a raising label that will display an error if the user has not entered an email with an <code>@</code> or <code>.</code> characters.</p>\n<p><include-content data-demo=\"reflect-value\"></include-content></p>\n<h4 id=\"md-range-slider\">Range slider</h4>\n<p><code>&lt;super-input&gt;</code> ships with advanced slider CSS. Add <code>[type=&quot;range&quot;]</code> to the <code>input</code>. <code>[reflect-value]</code> is required for this to work natively in Chromium browsers. Safari and Firefox will need some help via Quark or JS until they support <a href=\"https://caniuse.com/mdn-css_types_type\">the CSS <code>type()</code> function</a>. You must also set the min/max CSS variables to match the min/max on the input.</p>\n<p>Be cautious using this feature, as it aesthetically relies on non-standard pseudo elements for the time being.</p>\n<p><include-content data-demo=\"slider\"></include-content></p>\n"
22
+ },
23
+ "installation": {
24
+ "name": "@excom/super-input",
25
+ "shortName": "super-input",
26
+ "version": "0.1.0",
27
+ "description": "<super-input> custom element",
28
+ "packageType": "kit-element",
29
+ "cdn": "<script src=\"https://unpkg.com/@excom/kit-utils/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/neutron/dist/index.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@excom/super-input@0.1.0/dist/index.umd.min.js\"></script>\n<link rel=\"stylesheet\" href=\"https://unpkg.com/@excom/super-input@0.1.0/dist/index.css\">",
30
+ "install": {
31
+ "npm": "npm install @excom/super-input"
32
+ },
33
+ "imports": {
34
+ "js": "import \"@excom/super-input\";",
35
+ "css": "@import \"@excom/super-input\";",
36
+ "html": "<!-- import path to `node_modules` will depend on your build setup -->\n<script type=\"module\" src=\"/node_modules/@excom/super-input\"></script>\n<link rel=\"stylesheet\" href=\"/node_modules/@excom/super-input\">"
37
+ },
38
+ "peerDependencies": []
39
+ },
40
+ "elementApis": [
41
+ {
42
+ "tag": "super-input",
43
+ "kind": "class",
44
+ "attributes": [
45
+ {
46
+ "name": "auto-label",
47
+ "type": "boolean",
48
+ "description": "When set, generates or uses an existing <code>id</code> on the <code>&lt;input&gt;</code> and associates the <code>&lt;label&gt;</code>.",
49
+ "fieldName": "autoLabel",
50
+ "surface": "option"
51
+ },
52
+ {
53
+ "name": "invalid-message",
54
+ "type": "string",
55
+ "description": "If set, this message is installed via <code>setCustomValidity()</code> when the native <code>invalid</code> event fires, and cleared on the next <code>keydown</code>. Makes built-in HTML validation surface a custom message.",
56
+ "fieldName": "invalidMessage",
57
+ "surface": "option"
58
+ },
59
+ {
60
+ "name": "reflect-value",
61
+ "type": "boolean",
62
+ "description": "When set, the current input value is mirrored to the <code>current-value</code> attribute so CSS and selectors can respond to it. Off by default because reflecting every keystroke is not free.",
63
+ "fieldName": "reflectValue",
64
+ "surface": "option"
65
+ },
66
+ {
67
+ "name": "text-format",
68
+ "type": "string",
69
+ "description": "Pattern defining visible formatting. Use <code>x</code> for any digit and any other character as a literal. Examples: <code>(xxx) xxx-xxxx</code>, <code>xx/xx/xxxx</code>, <code>xxx-xx-xxxx</code>. When set, the input value is live-formatted on every keystroke.",
70
+ "fieldName": "textFormat",
71
+ "surface": "option"
72
+ },
73
+ {
74
+ "name": "current-value",
75
+ "type": "string",
76
+ "description": "Mirrors the wrapped <code>&lt;input&gt;</code>&#39;s value when <code>reflect-value</code> is set. Read-only from the app&#39;s point of view — writing it does not change the input.",
77
+ "fieldName": "currentValue",
78
+ "surface": "state"
79
+ }
80
+ ],
81
+ "events": [],
82
+ "slots": [],
83
+ "cssProperties": [
84
+ {
85
+ "name": "--super-input-accent",
86
+ "description": "Active track / accent color for the range fill.",
87
+ "syntax": "<color>",
88
+ "default": "var(--super-input-primary, blue)"
89
+ },
90
+ {
91
+ "name": "--super-input-bg",
92
+ "description": "Surface background behind the control (and raised-label mask).",
93
+ "syntax": "<color>",
94
+ "default": "var(--v-form-element-background-color, transparent)"
95
+ },
96
+ {
97
+ "name": "--super-input-border-color",
98
+ "description": "Default border color for raised inputs.",
99
+ "syntax": "<color>",
100
+ "default": "var( --v-form-element-border-color, currentColor )"
101
+ },
102
+ {
103
+ "name": "--super-input-border-radius",
104
+ "description": "Corner radius for raised inputs.",
105
+ "syntax": "<length>",
106
+ "default": "var(--v-border-radius, 4px)"
107
+ },
108
+ {
109
+ "name": "--super-input-border-width",
110
+ "description": "Border width for raised inputs.",
111
+ "syntax": "<length>",
112
+ "default": "var(--v-border-width, 1px)"
113
+ },
114
+ {
115
+ "name": "--super-input-color",
116
+ "description": "Foreground / label color.",
117
+ "syntax": "<color>",
118
+ "default": "var(--v-color, inherit)"
119
+ },
120
+ {
121
+ "name": "--super-input-focus-color",
122
+ "description": "Focus ring / focus border color.",
123
+ "syntax": "<color>",
124
+ "default": "var( --v-form-element-focus-color, var(--super-input-border-color) )"
125
+ },
126
+ {
127
+ "name": "--super-input-font-size",
128
+ "description": "Base font size for the control and value readout.",
129
+ "syntax": "<length>",
130
+ "default": "16px"
131
+ },
132
+ {
133
+ "name": "--super-input-font-weight",
134
+ "description": "Font weight for the range value readout.",
135
+ "syntax": "<number> | <integer>",
136
+ "default": "700"
137
+ },
138
+ {
139
+ "name": "--super-input-height",
140
+ "description": "Host height for range layout (thumb + value readout).",
141
+ "syntax": "<length>",
142
+ "default": "calc(var(--super-input-thumb-size) + 36px)"
143
+ },
144
+ {
145
+ "name": "--super-input-label-height",
146
+ "description": "Computed label line box height (used when a <code>&lt;label&gt;</code> is present).",
147
+ "syntax": "<length>",
148
+ "default": "calc( var(--v-line-height, 1.5) * var(--super-input-font-size) )"
149
+ },
150
+ {
151
+ "name": "--super-input-label-margin-bottom",
152
+ "description": "Gap between a floating label and the control.",
153
+ "syntax": "<length>",
154
+ "default": "calc(21px * 0.375)"
155
+ },
156
+ {
157
+ "name": "--super-input-max",
158
+ "description": "Range maximum. Prefer setting on the host (or via CSS).",
159
+ "syntax": "<number>",
160
+ "default": "100"
161
+ },
162
+ {
163
+ "name": "--super-input-min",
164
+ "description": "Range minimum. Prefer setting on the host (or via CSS): CSS cannot read the child input&#39;s <code>min</code> attribute from the parent.",
165
+ "syntax": "<number>",
166
+ "default": "0"
167
+ },
168
+ {
169
+ "name": "--super-input-primary",
170
+ "description": "Accent color (range fill, thumb border, focus chrome).",
171
+ "syntax": "<color>",
172
+ "default": "var(--v-primary, blue)"
173
+ },
174
+ {
175
+ "name": "--super-input-thumb-color",
176
+ "description": "Range thumb fill color.",
177
+ "syntax": "<color>",
178
+ "default": "var( --v-range-thumb-color, var(--super-input-bg) )"
179
+ },
180
+ {
181
+ "name": "--super-input-thumb-size",
182
+ "description": "Diameter of the range thumb.",
183
+ "syntax": "<length>",
184
+ "default": "44px"
185
+ },
186
+ {
187
+ "name": "--super-input-track-height",
188
+ "description": "Height of the range track.",
189
+ "syntax": "<length>",
190
+ "default": "2px"
191
+ },
192
+ {
193
+ "name": "--super-input-transition-duration",
194
+ "description": "Duration for border / label transitions.",
195
+ "syntax": "<time>",
196
+ "default": "var(--v-transition-duration-fast, 0.15s)"
197
+ },
198
+ {
199
+ "name": "--super-input-transition-ease",
200
+ "description": "Easing for border / label transitions.",
201
+ "syntax": "*",
202
+ "default": "var(--v-transition-ease-out, ease-out)"
203
+ },
204
+ {
205
+ "name": "--super-input-value",
206
+ "description": "Current range value used for fill / readout positioning. With <code>reflect-value</code>, may be driven from <code>attr(current-value)</code> where typed <code>attr()</code> is supported.",
207
+ "syntax": "<number>",
208
+ "default": "50"
209
+ },
210
+ {
211
+ "name": "--super-input-width",
212
+ "description": "Host width for range layout.",
213
+ "syntax": "<length> | <percentage>",
214
+ "default": "100%"
215
+ }
216
+ ],
217
+ "cssClasses": [
218
+ {
219
+ "name": "raised",
220
+ "description": "Raised / floating-label text field appearance. Apply as <code>class=&quot;raised&quot;</code> on <code>&lt;super-input&gt;</code> wrapping a text-like <code>&lt;input&gt;</code>."
221
+ }
222
+ ],
223
+ "cssAliases": [
224
+ {
225
+ "name": ":--super-input",
226
+ "selectors": [
227
+ "super-input",
228
+ ".tag-super-input"
229
+ ],
230
+ "kind": "element",
231
+ "description": ""
232
+ },
233
+ {
234
+ "name": ":--super-input--has-current-value",
235
+ "selectors": [
236
+ "[current-value]",
237
+ "[data-current-value]"
238
+ ],
239
+ "kind": "state",
240
+ "description": ""
241
+ },
242
+ {
243
+ "name": ":--super-input--has-reflect-value",
244
+ "selectors": [
245
+ "[reflect-value]",
246
+ "[data-reflect-value]"
247
+ ],
248
+ "kind": "state",
249
+ "description": ""
250
+ }
251
+ ],
252
+ "listens": [],
253
+ "commands": [],
254
+ "defaultActions": [],
255
+ "expectedChildren": [
256
+ {
257
+ "name": "descendant:input",
258
+ "relationship": "descendant",
259
+ "selector": "input",
260
+ "required": true,
261
+ "description": "Required native <code>&lt;input&gt;</code> to enhance."
262
+ },
263
+ {
264
+ "name": "descendant:label",
265
+ "relationship": "descendant",
266
+ "selector": "label",
267
+ "required": false,
268
+ "description": "Optional native <code>&lt;label&gt;</code>. When <code>auto-label</code> is set, its <code>for</code> attribute is linked to the input&#39;s <code>id</code>."
269
+ }
270
+ ],
271
+ "provisions": []
272
+ }
273
+ ],
274
+ "exportedFiles": {}
275
+ }
@@ -0,0 +1,22 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
9
+
10
+ describe("comprehensive view", () => {
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ it("combines format, invalid-message, and auto-label", async () => {
16
+ const { root } = await mountView(readDemo(import.meta.url, "comprehensive"));
17
+ const host = root.querySelector("super-input")!;
18
+ expect(host.hasAttribute("auto-label")).toBe(true);
19
+ expect(host.getAttribute("text-format")).toBe("(xxx) xxx-xxxx");
20
+ expect(host.getAttribute("invalid-message")).toMatch(/US phone/);
21
+ });
22
+ });
@@ -0,0 +1,21 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
9
+
10
+ describe("invalid-message view", () => {
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ it("exposes the custom invalid message", async () => {
16
+ const { root } = await mountView(readDemo(import.meta.url, "invalid-message"));
17
+ const host = root.querySelector("super-input")!;
18
+ expect(host.getAttribute("invalid-message")).toMatch(/US phone/);
19
+ expect(host.querySelector("input")?.required).toBe(true);
20
+ });
21
+ });
@@ -0,0 +1,24 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
9
+
10
+ describe("reflect-value view", () => {
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ it("reflects typed email onto current-value", async () => {
16
+ const { root } = await mountView(readDemo(import.meta.url, "reflect-value"));
17
+ const host = root as unknown as HTMLSuperInputElement;
18
+ const input = root.querySelector("input")!;
19
+ input.value = "a@b.com";
20
+ input.dispatchEvent(new Event("input", { bubbles: true }));
21
+ await flush();
22
+ expect(host.getAttribute("current-value")).toBe("a@b.com");
23
+ });
24
+ });
@@ -0,0 +1,24 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import { flush, mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
9
+
10
+ describe("slider view", () => {
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ it("reflects the range value", async () => {
16
+ const { root } = await mountView(readDemo(import.meta.url, "slider"));
17
+ const input = root.querySelector<HTMLInputElement>("input")!;
18
+ expect(input.value).toBe("36");
19
+ input.value = "40";
20
+ input.dispatchEvent(new Event("input", { bubbles: true }));
21
+ await flush();
22
+ expect(root.getAttribute("current-value")).toBe("40");
23
+ });
24
+ });
@@ -0,0 +1,245 @@
1
+ import {
2
+ afterEach,
3
+ describe,
4
+ expect,
5
+ fixture,
6
+ it,
7
+ wait,
8
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
9
+ import "../../index";
10
+
11
+ describe("super-input", () => {
12
+ afterEach(() => {
13
+ document.body.innerHTML = "";
14
+ });
15
+
16
+ it("[auto-label] creates new id if necessary", async () => {
17
+ const el = fixture<HTMLSuperInputElement>(
18
+ `<super-input auto-label>
19
+ <input />
20
+ <label></label>
21
+ </super-input>`,
22
+ );
23
+ const input = el.querySelector("input") as HTMLInputElement;
24
+ const label = el.querySelector("label") as HTMLLabelElement;
25
+ expect(input.id).toMatch(/super-input-/);
26
+ expect(input.id).toBe(label.htmlFor);
27
+ });
28
+
29
+ it("1. [auto-label] takes existing id if provided", async () => {
30
+ const el = fixture<HTMLSuperInputElement>(
31
+ `<super-input auto-label>
32
+ <input id="my-input" />
33
+ <label for="will-be-replaced"></label>
34
+ </super-input>`,
35
+ );
36
+ expect(el).dom.to.equalTag(
37
+ `<super-input auto-label>
38
+ <input id="my-input" />
39
+ <label for="my-input"></label>
40
+ </super-input>`,
41
+ );
42
+ });
43
+
44
+ it("2. [auto-label] takes existing id if provided", async () => {
45
+ const el = fixture<HTMLSuperInputElement>(
46
+ `<super-input auto-label>
47
+ <input />
48
+ <label for="my-input"></label>
49
+ </super-input>`,
50
+ );
51
+ expect(el).dom.to.equalTag(
52
+ `<super-input auto-label>
53
+ <input id="my-input" />
54
+ <label for="my-input"></label>
55
+ </super-input>`,
56
+ );
57
+ });
58
+
59
+ it("reflects input value when enabled", async () => {
60
+ const el = fixture<HTMLSuperInputElement>(
61
+ `<super-input reflect-value><input /></super-input>`,
62
+ );
63
+ const input = el.querySelector("input") as HTMLInputElement;
64
+ input.value = "hello";
65
+ input.dispatchEvent(new Event("input"));
66
+ await wait(0);
67
+ expect(el).dom.to.equalTag(
68
+ `<super-input reflect-value current-value="hello"></super-input>`,
69
+ );
70
+ });
71
+
72
+ it("does not reflect value when reflectValue is false", async () => {
73
+ const el = fixture<HTMLSuperInputElement>(
74
+ `<super-input><input /></super-input>`,
75
+ );
76
+ const input = el.querySelector("input") as HTMLInputElement;
77
+ input.value = "hello";
78
+ input.dispatchEvent(new Event("input"));
79
+ await wait(0);
80
+ expect(el).dom.to.equalTag(`<super-input></super-input>`);
81
+ });
82
+
83
+ it("formats input value configured in [text-format] and reflects that formatted value", async () => {
84
+ const el = fixture<HTMLSuperInputElement>(
85
+ `<super-input text-format="xx-xx-xxxx" reflect-value><input value="1234567890" /></super-input>`,
86
+ );
87
+ const input = el.querySelector("input")!;
88
+ expect(input.value).toBe("12-34-5678");
89
+ expect(el.currentValue).toBe("12-34-5678");
90
+ el.textFormat = "xxxx-xxxx-xxxx";
91
+ expect(input.value).toBe("1234-5678");
92
+ expect(el.currentValue).toBe("1234-5678");
93
+ input.value = "1234567890";
94
+ input.dispatchEvent(new Event("input", { bubbles: true }));
95
+ expect(input.value).toBe("1234-5678-90");
96
+ expect(el.currentValue).toBe("1234-5678-90");
97
+ el.textFormat = null;
98
+ expect(input.value).toBe("1234567890");
99
+ expect(el.currentValue).toBe("1234567890");
100
+ input.dispatchEvent(new Event("input", { bubbles: true }));
101
+ expect(input.value).toBe("1234567890");
102
+ expect(el.currentValue).toBe("1234567890");
103
+ input.value = "";
104
+ input.dispatchEvent(new Event("input", { bubbles: true }));
105
+ expect(input.value).toBe("");
106
+ expect(el.currentValue).toBe(null);
107
+ });
108
+
109
+ it("adds and removes event listeners when needed", async () => {
110
+ const el = fixture<HTMLSuperInputElement>(
111
+ `<super-input><input /></super-input>`,
112
+ );
113
+ const input = el._inputElement;
114
+ el.reflectValue = true;
115
+ expect(input).toMatchListeners({ input: 1 });
116
+ el.textFormat = "x-x"
117
+ expect(input).toMatchListeners({ input: 1 });
118
+ el.invalidMessage = "Invalid";
119
+ expect(input).toMatchListeners({ input: 1, invalid: 1, keydown: 1 });
120
+ el.reflectValue = false;
121
+ expect(input).toMatchListeners({ input: 1, invalid: 1, keydown: 1 });
122
+ el.textFormat = null;
123
+ expect(input).toMatchListeners({ invalid: 1, keydown: 1 });
124
+ el.invalidMessage = "test"
125
+ expect(input).toMatchListeners({ invalid: 1, keydown: 1 })
126
+ el.invalidMessage = null;
127
+ expect(input).toMatchListeners({});
128
+ });
129
+
130
+ it("does not push an external current-value write into the input", async () => {
131
+ const el = fixture<HTMLSuperInputElement>(
132
+ `<super-input><input value="1" /></super-input>`,
133
+ );
134
+ const input = el.querySelector("input") as HTMLInputElement;
135
+ input.value = "typed";
136
+ el.setAttribute("current-value", "42");
137
+ await wait(0);
138
+ expect(input.value).toBe("typed");
139
+ });
140
+ });
141
+
142
+ describe("super-input (edge cases)", () => {
143
+ afterEach(() => {
144
+ document.body.innerHTML = "";
145
+ });
146
+
147
+ it("[invalid-message] installs the custom validity on `invalid` and clears it on `keydown`", async () => {
148
+ const el = fixture<HTMLSuperInputElement>(
149
+ `<super-input invalid-message="Needs a value"><input required /></super-input>`,
150
+ );
151
+ const input = el.querySelector("input")!;
152
+ expect(input).toMatchListeners({ invalid: 1, keydown: 1 });
153
+
154
+ input.dispatchEvent(new Event("invalid"));
155
+ expect(input.validationMessage).toBe("Needs a value");
156
+ expect(input.validity.customError).toBe(true);
157
+
158
+ input.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
159
+ expect(input.validity.customError).toBe(false);
160
+ expect(input.validationMessage).not.toBe("Needs a value");
161
+ });
162
+
163
+ it("[invalid-message] does nothing on `invalid` once the message is unset", async () => {
164
+ const el = fixture<HTMLSuperInputElement>(
165
+ `<super-input invalid-message="Needs a value"><input required /></super-input>`,
166
+ );
167
+ const input = el.querySelector("input")!;
168
+ el.invalidMessage = null;
169
+ expect(input).toMatchListeners({});
170
+ // the listener is gone; invoking the handler directly is a no-op
171
+ el.handleInvalid();
172
+ expect(input.validity.customError).toBe(false);
173
+ input.dispatchEvent(new Event("invalid"));
174
+ expect(input.validity.customError).toBe(false);
175
+ });
176
+
177
+ it("[reflect-value] reflects `null` when the input is empty", async () => {
178
+ const el = fixture<HTMLSuperInputElement>(
179
+ `<super-input reflect-value><input value="abc" /></super-input>`,
180
+ );
181
+ const input = el.querySelector("input")!;
182
+ expect(el.currentValue).toBe("abc");
183
+ input.value = "";
184
+ input.dispatchEvent(new Event("input"));
185
+ expect(el.currentValue).toBe(null);
186
+ expect(el.hasAttribute("current-value")).toBe(false);
187
+ });
188
+
189
+ it("[reflect-value] clears current-value when reflection is switched off", async () => {
190
+ const el = fixture<HTMLSuperInputElement>(
191
+ `<super-input reflect-value><input value="abc" /></super-input>`,
192
+ );
193
+ expect(el.getAttribute("current-value")).toBe("abc");
194
+ el.reflectValue = false;
195
+ expect(el.currentValue).toBe(null);
196
+ const input = el.querySelector("input")!;
197
+ input.value = "typed";
198
+ input.dispatchEvent(new Event("input"));
199
+ expect(el.currentValue).toBe(null);
200
+ });
201
+
202
+ it("[text-format] formats a programmatic value when the format changes and keeps the caret-relative selection", async () => {
203
+ const el = fixture<HTMLSuperInputElement>(
204
+ `<super-input text-format="(xxx) xxx-xxxx"><input /></super-input>`,
205
+ );
206
+ const input = el.querySelector("input")!;
207
+ // an empty input is left alone by doFormat
208
+ el.textFormat = "xxx-xxx";
209
+ expect(input.value).toBe("");
210
+ input.value = "5551234";
211
+ input.dispatchEvent(new Event("input"));
212
+ expect(input.value).toBe("555-123");
213
+ // no reflect-value: the attribute is never written
214
+ expect(el.hasAttribute("current-value")).toBe(false);
215
+ });
216
+
217
+ it("tolerates a missing <input>", async () => {
218
+ const el = fixture<HTMLSuperInputElement>(
219
+ `<super-input reflect-value text-format="xx-xx" invalid-message="nope" auto-label>
220
+ <label>Orphan</label>
221
+ </super-input>`,
222
+ );
223
+ expect(el._inputElement).toBe(null);
224
+ expect(el.currentValue).toBe(null);
225
+ expect(el.querySelector("label")!.htmlFor).toBe("");
226
+ // prop toggles must not throw without an input
227
+ el.reflectValue = false;
228
+ el.textFormat = null;
229
+ el.invalidMessage = null;
230
+ expect(el.currentValue).toBe(null);
231
+ el.reflectValue = true;
232
+ el.textFormat = "xxx";
233
+ el.invalidMessage = "still nope";
234
+ expect(el.currentValue).toBe(null);
235
+ expect(el.hasAttribute("current-value")).toBe(false);
236
+ });
237
+
238
+ it("[auto-label] leaves the label alone when auto-label is unset", async () => {
239
+ const el = fixture<HTMLSuperInputElement>(
240
+ `<super-input><input /><label for="keep-me"></label></super-input>`,
241
+ );
242
+ expect(el.querySelector("input")!.id).toBe("");
243
+ expect(el.querySelector("label")!.htmlFor).toBe("keep-me");
244
+ });
245
+ });
@@ -0,0 +1,20 @@
1
+ import "../../index";
2
+ import {
3
+ afterEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
8
+ import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
9
+
10
+ describe("with-format view", () => {
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ it("formats the seeded phone number", async () => {
16
+ const { root } = await mountView(readDemo(import.meta.url, "with-format"));
17
+ const input = root.querySelector("input")!;
18
+ expect(input.value).toMatch(/\(\d{3}\)/);
19
+ });
20
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }