semantic-forms 5.1.12 → 5.2.1
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 +11 -0
- package/dist/semantic-forms.cjs +42 -7
- package/dist/semantic-forms.cjs.map +1 -1
- package/dist/semantic-forms.css +2004 -1237
- package/dist/semantic-forms.css.map +1 -1
- package/dist/semantic-forms.js +42 -7
- 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 +42 -7
- package/dist/semantic-forms.mjs.map +1 -1
- package/docs/statics/pages/fullDemo.html +214 -215
- package/docs/statics/pages/index.html +1 -0
- package/docs/statics/pages/reservedKeyboardShortcuts.html +840 -0
- package/docs/statics/pages/usage.html +48 -0
- package/package.json +3 -3
- package/semanticForms.js +193 -11
- package/semanticForms.scss +865 -465
|
@@ -132,6 +132,54 @@
|
|
|
132
132
|
</dl>
|
|
133
133
|
</form>
|
|
134
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
|
+
|
|
135
183
|
<h5 id="custominputfields">Customizing input fields</h5>
|
|
136
184
|
|
|
137
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>
|
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.1
|
|
11
|
+
"version": "5.2.1",
|
|
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
|
@@ -11,9 +11,84 @@ const semanticForms = () => {
|
|
|
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')
|
|
@@ -45,6 +120,10 @@ const semanticForms = () => {
|
|
|
45
120
|
input.classList.add('semanticform')
|
|
46
121
|
|
|
47
122
|
// #region create labels
|
|
123
|
+
|
|
124
|
+
const newLabel = document.createElement('label')
|
|
125
|
+
newLabel.className = 'floatLabelFormAnimatedLabel'
|
|
126
|
+
|
|
48
127
|
if (type === 'checkbox' || type === 'radio') {
|
|
49
128
|
// recursively find <dd> element
|
|
50
129
|
let dd = input.parentNode
|
|
@@ -56,9 +135,6 @@ const semanticForms = () => {
|
|
|
56
135
|
}
|
|
57
136
|
|
|
58
137
|
if (dd.firstChild.nodeName !== 'LABEL') {
|
|
59
|
-
const newLabel = document.createElement('label')
|
|
60
|
-
newLabel.className = 'floatLabelFormAnimatedLabel'
|
|
61
|
-
|
|
62
138
|
if (type === 'checkbox' && input.parentNode.nodeName === 'DD') {
|
|
63
139
|
newLabel.setAttribute('for', input.id)
|
|
64
140
|
input.parentNode.classList.add('singleCheckbox')
|
|
@@ -99,10 +175,7 @@ const semanticForms = () => {
|
|
|
99
175
|
div.append(label.closest('dt'), dd)
|
|
100
176
|
dl.append(div)
|
|
101
177
|
} else {
|
|
102
|
-
const newLabel = document.createElement('label')
|
|
103
178
|
newLabel.setAttribute('for', input.id)
|
|
104
|
-
newLabel.className = 'floatLabelFormAnimatedLabel'
|
|
105
|
-
|
|
106
179
|
newLabel.innerHTML = label.innerHTML
|
|
107
180
|
|
|
108
181
|
if (input.hasAttribute('title') && label.getAttribute('data-show-help-icon') !== null && !label.querySelector('span.help')) {
|
|
@@ -121,6 +194,97 @@ const semanticForms = () => {
|
|
|
121
194
|
}
|
|
122
195
|
// #endregion
|
|
123
196
|
|
|
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
|
+
|
|
124
288
|
// #region standard inputs
|
|
125
289
|
// check for auto-grow attribute on textareas
|
|
126
290
|
if (input.getAttribute('data-auto-grow') !== null) {
|
|
@@ -133,7 +297,6 @@ const semanticForms = () => {
|
|
|
133
297
|
newInput.setAttribute('data-auto-grow', '')
|
|
134
298
|
input.replaceWith(newInput)
|
|
135
299
|
input = newInput
|
|
136
|
-
|
|
137
300
|
}
|
|
138
301
|
|
|
139
302
|
if (input.nodeName === 'TEXTAREA') {
|
|
@@ -194,9 +357,9 @@ const semanticForms = () => {
|
|
|
194
357
|
dd.classList.remove(match)
|
|
195
358
|
div.classList.add(match)
|
|
196
359
|
}
|
|
197
|
-
|
|
360
|
+
|
|
198
361
|
// check for max-content attribute
|
|
199
|
-
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
362
|
+
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
200
363
|
if (input.getAttribute('data-max-content') !== null) {
|
|
201
364
|
if (!('fieldSizing' in document.createElement('input').style)) {
|
|
202
365
|
const adjustWidth = () => {
|
|
@@ -342,7 +505,7 @@ const semanticForms = () => {
|
|
|
342
505
|
}
|
|
343
506
|
|
|
344
507
|
// progressively enhance textarea for Firefox and Safari
|
|
345
|
-
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
508
|
+
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
346
509
|
if (input.getAttribute('data-auto-grow') !== null) {
|
|
347
510
|
const adjustHeight = () => {
|
|
348
511
|
if (input.value.length) {
|
|
@@ -380,6 +543,25 @@ const semanticForms = () => {
|
|
|
380
543
|
else referenceNode.parentNode.appendChild(newNode)
|
|
381
544
|
}
|
|
382
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
|
+
|
|
383
565
|
// handle undo/redo
|
|
384
566
|
let lastFocusedInput
|
|
385
567
|
let lastClearFieldPressed
|