semantic-forms 5.1.11 → 5.2.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.
- package/CHANGELOG.md +13 -0
- package/dist/semantic-forms.cjs +59 -10
- package/dist/semantic-forms.cjs.map +1 -1
- package/dist/semantic-forms.css +1834 -924
- package/dist/semantic-forms.css.map +1 -1
- package/dist/semantic-forms.js +59 -10
- package/dist/semantic-forms.js.map +1 -1
- package/dist/semantic-forms.min.js +1 -1
- package/dist/semantic-forms.min.js.map +1 -1
- package/dist/semantic-forms.min.mjs +1 -1
- package/dist/semantic-forms.min.mjs.map +1 -1
- package/dist/semantic-forms.mjs +59 -10
- package/dist/semantic-forms.mjs.map +1 -1
- package/docs/statics/pages/fullDemo.html +93 -0
- package/docs/statics/pages/index.html +1 -0
- package/docs/statics/pages/reservedKeyboardShortcuts.html +838 -0
- package/docs/statics/pages/usage.html +81 -0
- package/package.json +3 -3
- package/semanticForms.js +249 -11
- package/semanticForms.scss +490 -254
|
@@ -106,6 +106,80 @@
|
|
|
106
106
|
</dl>
|
|
107
107
|
</form>
|
|
108
108
|
|
|
109
|
+
<h5>Add inline buttons</h5>
|
|
110
|
+
|
|
111
|
+
<p>Include a button or input with the type "submit" in the <code><!--#<dd>--></code> element to place it next to the input:</p>
|
|
112
|
+
|
|
113
|
+
<pre><code class='language-html' lang='html'><escape>
|
|
114
|
+
<form class="semanticForms" method="post">
|
|
115
|
+
<dl>
|
|
116
|
+
<dt><label for="email">Email address</label></dt>
|
|
117
|
+
<dd>
|
|
118
|
+
<input id="email" name="email" type="email" placeholder="Enter an email address" autocomplete="off">
|
|
119
|
+
<button type="submit">Sign up</button>
|
|
120
|
+
</dd>
|
|
121
|
+
</dl>
|
|
122
|
+
</form>
|
|
123
|
+
</escape></code></pre>
|
|
124
|
+
|
|
125
|
+
<form class="semanticForms" method="post">
|
|
126
|
+
<dl>
|
|
127
|
+
<dt><label for="email">Email address</label></dt>
|
|
128
|
+
<dd>
|
|
129
|
+
<input id="email" name="email" type="email" placeholder="Enter an email address" autocomplete="off">
|
|
130
|
+
<button type="submit">Sign up!</button>
|
|
131
|
+
</dd>
|
|
132
|
+
</dl>
|
|
133
|
+
</form>
|
|
134
|
+
|
|
135
|
+
<h5>Add custom keyboard shortcuts</h5>
|
|
136
|
+
|
|
137
|
+
<p>Add keyboard shortcuts on specific inputs to focus them by adding a <code>data-focus-key</code> attribute and optional <code>data-focus-modifier</code> attribute.</p>
|
|
138
|
+
|
|
139
|
+
<pre><code class='language-html' lang='html'><escape>
|
|
140
|
+
<form class="semanticForms" method="post">
|
|
141
|
+
<dl>
|
|
142
|
+
<dt><label for="focus-1">Focus me!</label></dt>
|
|
143
|
+
<dd><input id="focus-1" name="focus-1" type="text" data-focus-key="Y"></dd>
|
|
144
|
+
|
|
145
|
+
<!-- change the modifier -->
|
|
146
|
+
<dt><label for="focus-2">Focus me!</label></dt>
|
|
147
|
+
<dd><input id="focus-2" name="focus-2" type="text" data-focus-key="Y" data-focus-modifier="alt"></dd>
|
|
148
|
+
|
|
149
|
+
<!-- specify modifier by OS -->
|
|
150
|
+
<dt><label for="focus-2">Focus me!</label></dt>
|
|
151
|
+
<dd><input id="focus-2" name="focus-2" type="text" data-focus-key="Z" data-focus-modifier-win="alt" data-focus-modifier-linux="meta" data-focus-modifier-mac="cmd"></dd>
|
|
152
|
+
</dl>
|
|
153
|
+
</form>
|
|
154
|
+
</escape></code></pre>
|
|
155
|
+
|
|
156
|
+
<form class="semanticForms" method="post">
|
|
157
|
+
<dl>
|
|
158
|
+
<dt><label for="focus-1">Focus me!</label></dt>
|
|
159
|
+
<dd><input id="focus-1" name="focus-1" type="text" data-focus-key="Y"></dd>
|
|
160
|
+
|
|
161
|
+
<!-- change the modifier -->
|
|
162
|
+
<dt><label for="focus-2">Focus me!</label></dt>
|
|
163
|
+
<dd><input id="focus-2" name="focus-2" type="text" data-focus-key="Y" data-focus-modifier="alt"></dd>
|
|
164
|
+
|
|
165
|
+
<!-- specify modifier by OS -->
|
|
166
|
+
<dt><label for="focus-3">Focus me!</label></dt>
|
|
167
|
+
<dd><input id="focus-3" name="focus-3" type="text" data-focus-key="Z" data-focus-modifier-win="alt" data-focus-modifier-linux="meta" data-focus-modifier-max="cmd"></dd>
|
|
168
|
+
</dl>
|
|
169
|
+
</form>
|
|
170
|
+
|
|
171
|
+
<ul>
|
|
172
|
+
<li><p>The focus key should be a single character, and is not case-sensitive. If multiple characters are detected, only the first one will be used. Special characters (!, @, #, etc.) will require <kbd>Shift</kbd> to be pressed with the keyboard shortcut.</p></li>
|
|
173
|
+
<li>
|
|
174
|
+
<p>The modifier key value may be "metactrl," "ctrl," "cmd," "meta," or "alt." The default value is "metactrl" and will automatically be applied if the <code>data-focus-modifier</code> attribute is not present.</p>
|
|
175
|
+
<ul>
|
|
176
|
+
<li>Some combinations are unable to be used due to conflicts with browser and OS keyboard shortcuts. See <a href="https://rooseveltframework.org/docs/semantic-forms/latest/reservedKeyboardShortcuts">reserved keyboard shortcuts</a> for a full overview.</li>
|
|
177
|
+
<li><p>OS-specific modifier keys can be set with <code>data-focus-modifier-win</code> (Windows), <code>data-focus-modifier-linux</code> (Linux) and <code>data-focus-modifier-mac</code> (Mac).</p></li>
|
|
178
|
+
<li><p>Due to a limited range of available shortcuts, the "Start" (win) key on Windows will be replaced with "Ctrl."</p></li>
|
|
179
|
+
</ul>
|
|
180
|
+
</li>
|
|
181
|
+
</ul>
|
|
182
|
+
|
|
109
183
|
<h5 id="custominputfields">Customizing input fields</h5>
|
|
110
184
|
|
|
111
185
|
<p>By default, input fields will display an X icon to clear the text and password fields will also display a show/hide toggle for displaying the password in plain text. You can customize this behavior as follows:</p>
|
|
@@ -121,6 +195,13 @@
|
|
|
121
195
|
<li>Note that checkboxes and radios should have a <code>title</code> attribute on the label instead of the inputs.</li>
|
|
122
196
|
</ul>
|
|
123
197
|
</li>
|
|
198
|
+
<li><p>Allow fixed-width inputs to grow/shrink to their longest content by applying a <code>data-max-content</code> attribute to the input.</p></li>
|
|
199
|
+
<li>
|
|
200
|
+
<p>Create a one-line text input that grows in height by applying a <code>data-auto-grow</code> attribute to a <code>textarea</code> or <code>input</code> (with type "text") element.</p>
|
|
201
|
+
<ul>
|
|
202
|
+
<li>Note that pressing <kbd>Enter</kbd> with this attribute will attempt to submit the form. Use <kbd>Shift + Enter</kbd> to add new lines.</li>
|
|
203
|
+
</ul>
|
|
204
|
+
</li>
|
|
124
205
|
</ul>
|
|
125
206
|
|
|
126
207
|
<p>You can also disable float labels on a per-field basis with the <code>no-float-label</code> class:</p>
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/rooseveltframework/semantic-forms/graphs/contributors"
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
|
-
"version": "5.
|
|
11
|
+
"version": "5.2.0",
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"semanticForms.scss",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"readmeFilename": "README.md",
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@jsdevtools/coverage-istanbul-loader": "3.0.5",
|
|
24
|
-
"@playwright/test": "1.56.
|
|
25
|
-
"eslint": "9.
|
|
24
|
+
"@playwright/test": "1.56.1",
|
|
25
|
+
"eslint": "9.38.0",
|
|
26
26
|
"eslint-plugin-html": "8.1.3",
|
|
27
27
|
"express": "5.1.0",
|
|
28
28
|
"nodemon": "3.1.10",
|
package/semanticForms.js
CHANGED
|
@@ -7,13 +7,88 @@ const semanticForms = () => {
|
|
|
7
7
|
|
|
8
8
|
const passwordShow = '<svg fill="none" height="256" viewBox="0 0 24 24" width="256" xmlns="http://www.w3.org/2000/svg"><g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m1 12s4-8 11-8 11 8 11 8"/><path d="m1 12s4 8 11 8 11-8 11-8"/><circle cx="12" cy="12" r="3"/></g></svg>'
|
|
9
9
|
const passwordHide = '<svg fill="none" height="256" viewBox="0 0 24 24" width="256" xmlns="http://www.w3.org/2000/svg"><g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m2 2 20 20"/><path d="m6.71277 6.7226c-3.04798 2.07267-4.71277 5.2774-4.71277 5.2774s3.63636 7 10 7c2.0503 0 3.8174-.7266 5.2711-1.7116m-6.2711-12.23018c.3254-.03809.6588-.05822 1-.05822 6.3636 0 10 7 10 7s-.6918 1.3317-2 2.8335"/><path d="m14 14.2362c-.5308.475-1.2316.7639-2 .7639-1.6569 0-3-1.3431-3-3 0-.8237.33193-1.5698.86932-2.11192"/></g></svg>'
|
|
10
|
-
const helpTextIcon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" version="1.1" style="overflow:visible;
|
|
10
|
+
const helpTextIcon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" version="1.1" style="overflow:visible;"><rect width="16" height="16" fill="none"/><path fill="currentColor" d="M8,10.5c-0.552,0-1,0.448-1,1c0,0.552,0.448,1,1,1c0.552,0,1-0.448,1-1C9,10.948,8.552,10.5,8,10.5z M8,0 C3.582,0,0,3.582,0,8c0,4.418,3.582,8,8,8s8-3.582,8-8C16,3.582,12.418,0,8,0z M12.243,12.243C11.109,13.376,9.603,14,8,14 s-3.109-0.624-4.243-1.757C2.624,11.109,2,9.603,2,8s0.624-3.109,1.757-4.243C4.891,2.624,6.397,2,8,2s3.109,0.624,4.243,1.757 C13.376,4.891,14,6.397,14,8C14,9.603,13.376,11.109,12.243,12.243z M7.927,3.755c-2.248,0-2.76,1.695-2.802,2.906h1.571 c0.028-0.256,0.101-1.418,1.221-1.418c0.672,0,1.206,0.406,1.206,1.175c0,0.695-0.724,1.155-1.303,1.711 C7.241,8.685,7.168,9.325,7.168,9.824V10c0.146,0,1.615,0,1.615,0V9.824c0-1.256,1.967-1.594,1.967-3.541 C10.75,5.059,9.874,3.755,7.927,3.755z"/></svg>'
|
|
11
11
|
|
|
12
12
|
const nodeNameLookup = ['TEXTAREA', 'SELECT']
|
|
13
13
|
const inputTypeLookup = ['checkbox', 'color', 'date', 'datetime-local', 'email', 'file', 'image', 'month', 'number', 'password', 'radio', 'range', 'search', 'tel', 'text', 'time', 'url', 'week']
|
|
14
|
+
const keyboardShortcuts = []
|
|
15
|
+
|
|
16
|
+
// custom keyboard shortcut listener
|
|
17
|
+
document.addEventListener('keydown', (e) => {
|
|
18
|
+
const specialCharMap = {
|
|
19
|
+
Minus: '-',
|
|
20
|
+
Equal: '=',
|
|
21
|
+
BracketLeft: '[',
|
|
22
|
+
BracketRight: ']',
|
|
23
|
+
Backslash: '\\',
|
|
24
|
+
Semicolon: ';',
|
|
25
|
+
Quote: '\'',
|
|
26
|
+
Comma: ',',
|
|
27
|
+
Period: '.',
|
|
28
|
+
Slash: '/',
|
|
29
|
+
Backquote: '`'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const shiftSpecialCharMap = {
|
|
33
|
+
1: '!',
|
|
34
|
+
2: '@',
|
|
35
|
+
3: '#',
|
|
36
|
+
4: '$',
|
|
37
|
+
5: '%',
|
|
38
|
+
6: '^',
|
|
39
|
+
7: '&',
|
|
40
|
+
8: '*',
|
|
41
|
+
9: '(',
|
|
42
|
+
0: ')',
|
|
43
|
+
Minus: '_',
|
|
44
|
+
Equal: '+',
|
|
45
|
+
BracketLeft: '{',
|
|
46
|
+
BracketRight: '}',
|
|
47
|
+
Backslash: '|',
|
|
48
|
+
Semicolon: ':',
|
|
49
|
+
Quote: '"',
|
|
50
|
+
Comma: '<',
|
|
51
|
+
Period: '>',
|
|
52
|
+
Slash: '?',
|
|
53
|
+
Backquote: '~'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const shortcut = keyboardShortcuts.find(shortcut => {
|
|
57
|
+
let matchesKey = false
|
|
58
|
+
if (e.altKey && !e.shiftKey) {
|
|
59
|
+
// mac adjusts the key value if altKey is pressed
|
|
60
|
+
matchesKey = 'Key' + shortcut.key.toUpperCase() === e.code ||
|
|
61
|
+
'Digit' + shortcut.key.toUpperCase() === e.code ||
|
|
62
|
+
shortcut.key === e.key ||
|
|
63
|
+
specialCharMap[e.code] === shortcut.key
|
|
64
|
+
} else if (e.shiftKey) {
|
|
65
|
+
// check shift special character map
|
|
66
|
+
const code = e.code.replace(/Key|Digit/, '')
|
|
67
|
+
matchesKey = (shiftSpecialCharMap[code] || shiftSpecialCharMap[e.key]) &&
|
|
68
|
+
(shiftSpecialCharMap[code] === shortcut.key || shiftSpecialCharMap[e.key] === shortcut.key)
|
|
69
|
+
} else {
|
|
70
|
+
matchesKey = shortcut.key.toUpperCase() === e.key.toUpperCase()
|
|
71
|
+
}
|
|
72
|
+
if (!matchesKey) return false
|
|
73
|
+
|
|
74
|
+
let matchesModifier
|
|
75
|
+
if (shortcut.modifier) {
|
|
76
|
+
if (shortcut.modifier === shortcut.defaultModifier) matchesModifier = shortcut.os === 'windows' || shortcut.os === 'linux' ? e.ctrlKey : e.metaKey
|
|
77
|
+
if (shortcut.modifier === 'meta') matchesModifier = e.metaKey
|
|
78
|
+
if (shortcut.modifier === 'alt') matchesModifier = e.altKey
|
|
79
|
+
if (shortcut.modifier === 'ctrl') matchesModifier = e.ctrlKey
|
|
80
|
+
}
|
|
81
|
+
return matchesModifier
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
if (shortcut) {
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
shortcut.input.focus()
|
|
87
|
+
}
|
|
88
|
+
})
|
|
14
89
|
|
|
15
90
|
// progressively enhance form elements that have the semanticForms class
|
|
16
|
-
const forms = document.querySelectorAll('form.semanticForms:not(.semanticFormsActive)')
|
|
91
|
+
const forms = document.querySelectorAll('form.semanticForms:not(.semanticFormsActive), table.semanticForms:not(.semanticFormsActive)')
|
|
17
92
|
|
|
18
93
|
for (const form of forms) {
|
|
19
94
|
form.classList.add('semanticFormsActive')
|
|
@@ -21,7 +96,7 @@ const semanticForms = () => {
|
|
|
21
96
|
|
|
22
97
|
// update each input in the semantic form
|
|
23
98
|
const inputs = Array.from(form.querySelectorAll('input, textarea, select'))
|
|
24
|
-
for (
|
|
99
|
+
for (let input of inputs) {
|
|
25
100
|
// ignore input if it has previously been formatted
|
|
26
101
|
if (input.classList.contains('semanticform') || !input.id) continue
|
|
27
102
|
|
|
@@ -46,6 +121,9 @@ const semanticForms = () => {
|
|
|
46
121
|
|
|
47
122
|
// #region create labels
|
|
48
123
|
|
|
124
|
+
const newLabel = document.createElement('label')
|
|
125
|
+
newLabel.className = 'floatLabelFormAnimatedLabel'
|
|
126
|
+
|
|
49
127
|
if (type === 'checkbox' || type === 'radio') {
|
|
50
128
|
// recursively find <dd> element
|
|
51
129
|
let dd = input.parentNode
|
|
@@ -57,9 +135,6 @@ const semanticForms = () => {
|
|
|
57
135
|
}
|
|
58
136
|
|
|
59
137
|
if (dd.firstChild.nodeName !== 'LABEL') {
|
|
60
|
-
const newLabel = document.createElement('label')
|
|
61
|
-
newLabel.className = 'floatLabelFormAnimatedLabel'
|
|
62
|
-
|
|
63
138
|
if (type === 'checkbox' && input.parentNode.nodeName === 'DD') {
|
|
64
139
|
newLabel.setAttribute('for', input.id)
|
|
65
140
|
input.parentNode.classList.add('singleCheckbox')
|
|
@@ -100,10 +175,7 @@ const semanticForms = () => {
|
|
|
100
175
|
div.append(label.closest('dt'), dd)
|
|
101
176
|
dl.append(div)
|
|
102
177
|
} else {
|
|
103
|
-
const newLabel = document.createElement('label')
|
|
104
178
|
newLabel.setAttribute('for', input.id)
|
|
105
|
-
newLabel.className = 'floatLabelFormAnimatedLabel'
|
|
106
|
-
|
|
107
179
|
newLabel.innerHTML = label.innerHTML
|
|
108
180
|
|
|
109
181
|
if (input.hasAttribute('title') && label.getAttribute('data-show-help-icon') !== null && !label.querySelector('span.help')) {
|
|
@@ -122,7 +194,121 @@ const semanticForms = () => {
|
|
|
122
194
|
}
|
|
123
195
|
// #endregion
|
|
124
196
|
|
|
125
|
-
//
|
|
197
|
+
// #region keyboard shortcuts
|
|
198
|
+
if (input.getAttribute('data-focus-key') !== null) {
|
|
199
|
+
function handleKeyboardShortcut () {
|
|
200
|
+
const os = getOS()
|
|
201
|
+
// this is the custom keyword for meta on linux/mac, ctrl on windows
|
|
202
|
+
const defaultModifier = 'metactrl'
|
|
203
|
+
|
|
204
|
+
// get focus key value
|
|
205
|
+
let focusKey = input.getAttribute('data-focus-key')
|
|
206
|
+
if (focusKey.length > 1) {
|
|
207
|
+
console.error(`Provided focus key "${focusKey}" is more than one character. Using first character only.`)
|
|
208
|
+
focusKey = focusKey.toString()[0]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// get focus modifier value
|
|
212
|
+
let modifierSymbol
|
|
213
|
+
let modifierKey = defaultModifier
|
|
214
|
+
const modifierAttr = {
|
|
215
|
+
default: input.getAttribute('data-focus-modifier') || defaultModifier,
|
|
216
|
+
linux: input.getAttribute('data-focus-modifier-linux'),
|
|
217
|
+
mac: input.getAttribute('data-focus-modifier-mac'),
|
|
218
|
+
windows: input.getAttribute('data-focus-modifier-win')
|
|
219
|
+
}
|
|
220
|
+
if (os && modifierAttr[os]) {
|
|
221
|
+
// a specific modifier key has been set by the user
|
|
222
|
+
modifierKey = modifierAttr[os]
|
|
223
|
+
} else {
|
|
224
|
+
modifierKey = modifierAttr.default
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// validate passed in modifier
|
|
228
|
+
const recognizedModifiers = ['ctrl', 'alt', 'opt', 'meta', 'cmd', defaultModifier]
|
|
229
|
+
if (!recognizedModifiers.includes(modifierKey)) {
|
|
230
|
+
console.error(`Received an unrecognized modifier, "${modifierKey}," defaulting to "${defaultModifier}."`, input)
|
|
231
|
+
modifierKey = defaultModifier
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// retrieve modifier symbol
|
|
235
|
+
if (['alt', 'opt'].includes(modifierKey)) {
|
|
236
|
+
modifierSymbol = os === 'mac' ? '⌥' : '⎇'
|
|
237
|
+
} else if (['meta', 'win', 'cmd'].includes(modifierKey) || (modifierKey === defaultModifier && os === 'mac')) {
|
|
238
|
+
if (os === 'mac') {
|
|
239
|
+
modifierSymbol = '⌘'
|
|
240
|
+
} else if (os === 'linux') {
|
|
241
|
+
modifierSymbol = '◆'
|
|
242
|
+
} else {
|
|
243
|
+
modifierSymbol = '⊞'
|
|
244
|
+
}
|
|
245
|
+
} else if (modifierKey === 'ctrl' || (modifierKey === defaultModifier && (os === 'windows' || os === 'linux'))) {
|
|
246
|
+
if (os === 'mac') {
|
|
247
|
+
modifierSymbol = '⌃'
|
|
248
|
+
} else {
|
|
249
|
+
modifierSymbol = 'Ctrl'
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// add the shortcut to the cached array, if not a duplicate
|
|
254
|
+
if (keyboardShortcuts.some(shortcut => shortcut.key === focusKey && shortcut.modifier === modifierKey)) {
|
|
255
|
+
console.error(`Duplicate keyboard shortcut "${modifierKey} + ${focusKey}" detected. Only the first input will be focusable using this keyboard shortcut.`, input)
|
|
256
|
+
} else {
|
|
257
|
+
keyboardShortcuts.push({
|
|
258
|
+
key: focusKey,
|
|
259
|
+
modifier: modifierKey,
|
|
260
|
+
input,
|
|
261
|
+
os,
|
|
262
|
+
defaultModifier
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// set the shortcut indicator/title
|
|
267
|
+
if (input.nodeName === 'TEXTAREA' || input.type === 'text' || input.type === 'number') {
|
|
268
|
+
// create focus indicator for valid inputs
|
|
269
|
+
const indicator = document.createElement('span')
|
|
270
|
+
indicator.classList.add('focus-key')
|
|
271
|
+
indicator.innerHTML = `<kbd>${modifierSymbol} ${focusKey.toUpperCase()}</kbd>`
|
|
272
|
+
insertAfter(indicator, newLabel)
|
|
273
|
+
} else {
|
|
274
|
+
// update the input title
|
|
275
|
+
if (input.getAttribute('title')) {
|
|
276
|
+
input.setAttribute('title', input.getAttribute('title') + ` (${modifierSymbol} + ${focusKey})`)
|
|
277
|
+
} else {
|
|
278
|
+
input.setAttribute('title', `Focus with ${modifierSymbol} + ${focusKey}`)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// placed in a function so that it may exit while still completing other semantic-form enhancements
|
|
284
|
+
handleKeyboardShortcut()
|
|
285
|
+
}
|
|
286
|
+
// #endregion
|
|
287
|
+
|
|
288
|
+
// #region standard inputs
|
|
289
|
+
// check for auto-grow attribute on textareas
|
|
290
|
+
if (input.getAttribute('data-auto-grow') !== null) {
|
|
291
|
+
// progressively enhance inputs into textareas
|
|
292
|
+
if (input.nodeName === 'INPUT' && input.type === 'text') {
|
|
293
|
+
const newInput = document.createElement('textarea')
|
|
294
|
+
newInput.id = input.id
|
|
295
|
+
newInput.class = input.class
|
|
296
|
+
newInput.innerText = input.value
|
|
297
|
+
newInput.setAttribute('data-auto-grow', '')
|
|
298
|
+
input.replaceWith(newInput)
|
|
299
|
+
input = newInput
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (input.nodeName === 'TEXTAREA') {
|
|
303
|
+
// when pressing enter while this input is focused, we want to submit
|
|
304
|
+
input.addEventListener('keypress', (e) => {
|
|
305
|
+
if (e.key !== 'Enter' || (e.key === 'Enter' && e.shiftKey)) return
|
|
306
|
+
e.preventDefault()
|
|
307
|
+
form.requestSubmit()
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
126
312
|
if (type !== 'checkbox' && type !== 'radio') {
|
|
127
313
|
if (!input.getAttribute('placeholder')) input.setAttribute('placeholder', ' ')
|
|
128
314
|
|
|
@@ -139,7 +325,7 @@ const semanticForms = () => {
|
|
|
139
325
|
continue
|
|
140
326
|
}
|
|
141
327
|
|
|
142
|
-
// #region clear button
|
|
328
|
+
// #region input clear button
|
|
143
329
|
if (input.nodeName !== 'SELECT' && type !== 'range') {
|
|
144
330
|
const clearBtn = document.createElement('button')
|
|
145
331
|
clearBtn.type = 'button'
|
|
@@ -172,12 +358,29 @@ const semanticForms = () => {
|
|
|
172
358
|
div.classList.add(match)
|
|
173
359
|
}
|
|
174
360
|
|
|
361
|
+
// check for max-content attribute
|
|
362
|
+
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
363
|
+
if (input.getAttribute('data-max-content') !== null) {
|
|
364
|
+
if (!('fieldSizing' in document.createElement('input').style)) {
|
|
365
|
+
const adjustWidth = () => {
|
|
366
|
+
const value = input.value !== '' ? input.value : input.placeholder
|
|
367
|
+
const width = value.length * 8 + 40
|
|
368
|
+
input.style.width = width + 'px'
|
|
369
|
+
input.style.maxWidth = '100%'
|
|
370
|
+
div.style.width = width + 'px'
|
|
371
|
+
}
|
|
372
|
+
adjustWidth()
|
|
373
|
+
input.addEventListener('input', adjustWidth)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
175
377
|
div.append(dt, dd)
|
|
176
378
|
dl.append(div)
|
|
177
379
|
|
|
178
380
|
// determine visibility of newly created <div>
|
|
179
381
|
if (dt.style.display === 'none' && dd.style.display === 'none') div.style.display = 'none'
|
|
180
382
|
}
|
|
383
|
+
// #endregion
|
|
181
384
|
|
|
182
385
|
// handle file input clear btn, cannot be handled with CSS
|
|
183
386
|
if (type === 'file') {
|
|
@@ -301,6 +504,22 @@ const semanticForms = () => {
|
|
|
301
504
|
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
302
505
|
}
|
|
303
506
|
|
|
507
|
+
// progressively enhance textarea for Firefox and Safari
|
|
508
|
+
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
509
|
+
if (input.getAttribute('data-auto-grow') !== null) {
|
|
510
|
+
const adjustHeight = () => {
|
|
511
|
+
if (input.value.length) {
|
|
512
|
+
input.style.height = input.scrollHeight + 'px'
|
|
513
|
+
} else {
|
|
514
|
+
input.style.height = window.getComputedStyle(form).getPropertyValue('--semanticFormsInputHeight')
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// set initial height to semantic-forms CSS variable
|
|
519
|
+
input.style.height = window.getComputedStyle(form).getPropertyValue('--semanticFormsInputHeight')
|
|
520
|
+
input.addEventListener('input', adjustHeight)
|
|
521
|
+
}
|
|
522
|
+
|
|
304
523
|
// shifts the clear button to the right if a scrollbar is present
|
|
305
524
|
const shiftClearBtn = () => {
|
|
306
525
|
const clearBtn = input.parentElement?.querySelector('button.clear')
|
|
@@ -324,6 +543,25 @@ const semanticForms = () => {
|
|
|
324
543
|
else referenceNode.parentNode.appendChild(newNode)
|
|
325
544
|
}
|
|
326
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Uses the navigator to best determine the clients operating system.
|
|
548
|
+
* @returns Operating system string (`mac`, `windows`, `linux`)
|
|
549
|
+
*/
|
|
550
|
+
function getOS () {
|
|
551
|
+
const userAgent = window.navigator.userAgent
|
|
552
|
+
const platform = window.navigator.platform
|
|
553
|
+
let os = null
|
|
554
|
+
|
|
555
|
+
if (platform.includes('Win')) {
|
|
556
|
+
os = 'windows'
|
|
557
|
+
} else if (platform.includes('Mac') || /iPhone|iPad|iPod/.test(userAgent)) {
|
|
558
|
+
os = 'mac'
|
|
559
|
+
} else if (platform.includes('Linux') || /Android/.test(userAgent)) {
|
|
560
|
+
os = 'linux'
|
|
561
|
+
}
|
|
562
|
+
return os
|
|
563
|
+
}
|
|
564
|
+
|
|
327
565
|
// handle undo/redo
|
|
328
566
|
let lastFocusedInput
|
|
329
567
|
let lastClearFieldPressed
|