semantic-forms 5.1.1 → 5.1.3

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.
@@ -8,12 +8,12 @@
8
8
  <meta name="description" content="Describe your website here">
9
9
  <!--! this inline script makes it possible for check-if-CSS-is-disabled to listen for any CSS files that fail to load -->
10
10
  <script>window.addEventListener('error', (event) => { if (event?.target?.tagName === 'LINK') { window.linkTagError = true } }, true)</script>
11
- <link rel="preload" href="/css/styles.css" as="style">
12
- <link rel="preload" href="/js/main.js" as="script">
13
- <link rel="stylesheet" href="/css/styles.css">
11
+ <link rel="preload" href="/css/semantic-forms-styles.css" as="style">
12
+ <link rel="preload" href="/js/semantic-forms-main.js" as="script">
13
+ <link rel="stylesheet" href="/css/semantic-forms-styles.css">
14
14
  <link rel="stylesheet" href="/css/highlight.js.light.css">
15
15
  <link rel="stylesheet" href="/css/highlight.js.dark.css" media="(prefers-color-scheme: dark)">
16
- <script src="/js/main.js" defer></script>
16
+ <script src="/js/semantic-forms-main.js" defer></script>
17
17
  </head>
18
18
  <body class="no-js" id="semantic-forms-usage">
19
19
  <main>
@@ -25,6 +25,7 @@
25
25
  <ul>
26
26
  <li><a href="/index.html">About</a></li>
27
27
  <li><a href="/usage.html">Usage</a></li>
28
+ <li><a href="/fullDemo.html">Full demo</a></li>
28
29
  </ul>
29
30
  </nav>
30
31
  {pageContent|s}
@@ -503,6 +503,26 @@
503
503
  </dl>
504
504
  </form>
505
505
 
506
+ <h4 id="rangeInputsThatDisplayValues">Range inputs that display values</h4>
507
+
508
+ <p>Add a class of <code>displayValue</code> to any <code><!--#<input type="range">--></code> element to make its value display on the label:</p>
509
+
510
+ <pre><code class='language-html' lang='html'><escape>
511
+ <form class="semanticForms">
512
+ <dl>
513
+ <dt><label for="rangeValue">Range value</label></dt>
514
+ <dd><input id="rangeValue" name="rangeValue" type="range" class="displayValue"></dd>
515
+ </dl>
516
+ </form>
517
+ </escape></code></pre>
518
+
519
+ <form class="semanticForms">
520
+ <dl>
521
+ <dt><label for="rangeValue">Range value</label></dt>
522
+ <dd><input id="rangeValue" name="rangeValue" type="range" class="displayValue"></dd>
523
+ </dl>
524
+ </form>
525
+
506
526
  <h3 id="lightanddarkmode">Light mode and dark mode</h3>
507
527
 
508
528
  <p>Light mode and dark mode will be detected automatically based on operating system preference. To set it manually, you can apply an additional class of <code>light</code> or <code>dark</code> to your <code><!--#<form>--></code> elements to force the mode.</p>
@@ -534,7 +554,7 @@ form.semanticForms {
534
554
 
535
555
  <h3 id="more">More</h3>
536
556
 
537
- <p>See a page demoing all features <a href="https://rooseveltframework.github.io/semantic-forms/test/semanticForms.html">here</a>.</p>
557
+ <p>See a page demoing all features <a href="https://rooseveltframework.org/docs/semantic-forms/latest/fullDemo/">here</a>.</p>
538
558
  </article>
539
559
  </arg>
540
560
  </include>
package/package.json CHANGED
@@ -8,11 +8,11 @@
8
8
  "url": "https://github.com/rooseveltframework/semantic-forms/graphs/contributors"
9
9
  }
10
10
  ],
11
- "version": "5.1.1",
11
+ "version": "5.1.3",
12
12
  "files": [
13
13
  "dist",
14
14
  "semanticForms.scss",
15
- "docs/statics/pages",
15
+ "docs/statics",
16
16
  "*.md"
17
17
  ],
18
18
  "homepage": "https://rooseveltframework.org/docs/semantic-forms/latest",
@@ -21,25 +21,25 @@
21
21
  "readmeFilename": "README.md",
22
22
  "devDependencies": {
23
23
  "@jsdevtools/coverage-istanbul-loader": "3.0.5",
24
- "@playwright/test": "1.54.1",
25
- "eslint": "9.31.0",
24
+ "@playwright/test": "1.54.2",
25
+ "eslint": "9.33.0",
26
26
  "eslint-plugin-html": "8.1.3",
27
27
  "express": "5.1.0",
28
28
  "nodemon": "3.1.10",
29
29
  "nyc": "17.1.0",
30
- "sass": "1.89.2",
31
- "serve": "14.2.4",
30
+ "sass": "1.90.0",
31
+ "http-server": "14.1.1",
32
32
  "standard": "17.1.2",
33
33
  "webpack-cli": "6.0.1"
34
34
  },
35
35
  "scripts": {
36
36
  "build": "webpack && sass semanticForms.scss dist/semantic-forms.css",
37
37
  "watch": "webpack && sass --watch semanticForms.scss dist/semantic-forms.css",
38
- "d": "nodemon --exec \"npm run build && serve\"",
39
- "dev": "nodemon --exec \"npm run build && serve\"",
40
- "development": "nodemon --exec \"npm run build && serve\"",
41
- "lint": "standard && standard --plugin html test/*.html",
42
- "lint-fix": "standard --fix && standard --plugin html test/*.html --fix",
38
+ "d": "nodemon dev-server.js --ext js,scss",
39
+ "dev": "nodemon dev-server.js --ext js,scss",
40
+ "development": "nodemon dev-server.js --ext js,scss",
41
+ "lint": "standard && standard --plugin html docs/statics/pages/*.html",
42
+ "lint-fix": "standard --fix && standard --plugin html docs/statics/pages/*.html --fix",
43
43
  "test": "npm run build && playwright test",
44
44
  "coverage": "webpack --env coverage && sass semanticForms.scss dist/semantic-forms.css && nyc playwright test && npm run build"
45
45
  },
package/semanticForms.js CHANGED
@@ -68,6 +68,14 @@ const semanticForms = () => {
68
68
  insertAfter(newLabel, input)
69
69
  }
70
70
 
71
+ if (type === 'radio' && input.parentNode.nodeName === 'DD') {
72
+ newLabel.setAttribute('for', input.id)
73
+ input.parentNode.classList.add('singleRadio')
74
+ newLabel.className = ''
75
+ label.setAttribute('hidden', 'hidden')
76
+ insertAfter(newLabel, input)
77
+ }
78
+
71
79
  newLabel.innerHTML = label.innerHTML
72
80
 
73
81
  if (label.hasAttribute('title') && label.getAttribute('data-show-help-icon') !== null && !label.querySelector('span.help')) {
@@ -141,7 +149,7 @@ const semanticForms = () => {
141
149
  clearBtn.innerHTML = '<svg viewBox="0 0 16 16" width="18" height="18"><path d="M 1 1 L 15 15 M 1 15 L 15 1" fill="none" stroke-width="2" stroke="currentColor" />'
142
150
  clearBtn.classList.add('clear')
143
151
  clearBtn.id = `semanticFormsClearButton_${input.id}`
144
- clearBtn.addEventListener('click', (event) => {
152
+ clearBtn.addEventListener('click', () => {
145
153
  input.previousValue = input.value
146
154
  input.value = ''
147
155
  input.focus()
@@ -149,7 +157,7 @@ const semanticForms = () => {
149
157
  })
150
158
  insertAfter(clearBtn, dd.querySelector('label'))
151
159
  }
152
- input.addEventListener('focus', (event) => {
160
+ input.addEventListener('focus', event => {
153
161
  if (event.target.nodeName === 'INPUT') lastFocusedInput = event.target
154
162
  })
155
163
  // #endregion
@@ -171,14 +179,24 @@ const semanticForms = () => {
171
179
  // handle file input clear btn, cannot be handled with CSS
172
180
  if (type === 'file') {
173
181
  const clearBtn = input.parentElement.querySelector('.clear')
174
- input.addEventListener('input', e => {
175
- clearBtn.style.display = e.target.files.length ? 'flex' : 'none'
182
+ input.addEventListener('input', event => {
183
+ clearBtn.style.display = event.target.files.length ? 'flex' : 'none'
176
184
  })
177
185
  clearBtn.addEventListener('click', () => {
178
186
  clearBtn.style.display = 'none'
179
187
  })
180
188
  }
181
189
 
190
+ // handle range inputs with a class to display the value
191
+ if (type === 'range' && input.classList.contains('displayValue')) {
192
+ const label = input.parentNode.parentNode.querySelector('dd label')
193
+ label.innerHTML += `<span class="seperator">: </span><output>${input.value}</output>`
194
+ input.addEventListener('input', event => {
195
+ const output = event.target.parentNode.parentNode.querySelector('output')
196
+ output.innerHTML = event.target.value
197
+ })
198
+ }
199
+
182
200
  // #region show password button
183
201
  if (type === 'password' && input.getAttribute('data-no-reveal') === null) {
184
202
  const showBtn = document.createElement('button')
@@ -194,7 +212,7 @@ const semanticForms = () => {
194
212
  console.error(`semantic-forms: Found an input (${input.id || input.getAttribute('name')}) that is not inside a <dd> element.`)
195
213
  continue
196
214
  }
197
- showBtn.addEventListener('click', (event) => {
215
+ showBtn.addEventListener('click', () => {
198
216
  if (input.type === 'password') {
199
217
  showBtn.innerHTML = passwordHide
200
218
  showBtn.title = input.getAttribute('data-hide-password-text') || 'Hide password'
@@ -240,7 +258,7 @@ const semanticForms = () => {
240
258
  // handle undo/redo
241
259
  let lastFocusedInput
242
260
  let lastClearFieldPressed
243
- document.addEventListener('keydown', (event) => {
261
+ document.addEventListener('keydown', event => {
244
262
  if ((event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey) {
245
263
  // undo clearing a field
246
264
  if (lastFocusedInput) {
@@ -266,7 +284,7 @@ const semanticForms = () => {
266
284
 
267
285
  // monitor changes to the DOM and enhance new semanticForms forms that get added
268
286
  if (!window.semanticFormsObserver) {
269
- window.semanticFormsObserver = new window.MutationObserver((mutations) => {
287
+ window.semanticFormsObserver = new window.MutationObserver(mutations => {
270
288
  let stop = false
271
289
  for (const mutation of mutations) {
272
290
  for (const node of mutation.addedNodes) {
@@ -281,7 +299,7 @@ const semanticForms = () => {
281
299
  window.semanticFormsObserver.observe(document.body, { attributes: false, childList: true, characterData: false, subtree: true })
282
300
  }
283
301
 
284
- semanticForms.reinitialize = (form) => {
302
+ semanticForms.reinitialize = form => {
285
303
  form.classList.remove('semanticFormsActive')
286
304
  semanticForms()
287
305
  }
@@ -1,5 +1,5 @@
1
1
  // #region global styles
2
- form.semanticForms, form.semanticForms.light {
2
+ form.semanticForms, form.semanticForms.light, table.semanticForms {
3
3
  --semanticFormsFontFamily: sans-serif;
4
4
  --semanticFormsFormBgColor: #fff;
5
5
  --semanticFormsFormSubBgColor: #f5f5f5;
@@ -22,8 +22,14 @@ form.semanticForms, form.semanticForms.light {
22
22
  --semanticFormsNestedFieldsetBorder: 1px rgba(0, 0, 0, .2) solid;
23
23
  --semanticFormsNestedInputBgColor: #fff;
24
24
  --semanticFormsBorder: 1px var(--semanticFormsBorderColor) solid;
25
+ --semanticFormsTableTitleColor: #fcfcfc;
26
+ --semanticFormsTableHeaderColor: #eeeeee;
27
+ --semanticFormsTableOddColor: #dddddd;
28
+ --semanticFormsTableEvenColor: #cccccc;
29
+ --semanticFormsTableBorder: 1px #c0c0c0 solid;
25
30
  --semanticFormsSelectIcon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23000'%3E%3Cpolygon points='0,0 100,0 50,50'/%3E%3C/svg%3E");
26
- --semanticFormsCustomResizer: url("data:image/svg+xml;utf8,%3Csvg width='20' height='20' viewBox='0 0 5.2916665 5.2916666' version='1.1' id='svg5' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs id='defs2' /%3E%3Cg id='layer1'%3E%3Cpath style='fill:%23aaa;stroke:%23aaa;stroke-width:0.264583;stroke-dasharray:none;stroke-opacity:1' d='M 2.8245112,5.0994569 C 4.9396993,2.9842685 4.9396993,2.9842685 4.9396993,2.9842685' id='path1034' /%3E%3Cpath style='fill:%23aaa;stroke:%23aaa;stroke-width:0.264583;stroke-dasharray:none;stroke-opacity:1' d='M 1.515747,5.0406977 C 4.8873304,1.6691138 4.8873304,1.6691138 4.8873304,1.6691138' id='path1034-1' /%3E%3Cpath style='fill:%23aaa;stroke:%23aaa;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1' d='M 0.20657789,5.0909782 C 4.9312207,0.3663348 4.9312207,0.3663348 4.9312207,0.3663348' id='path1034-9' /%3E%3C/g%3E%3C/svg%3E")
31
+ --semanticFormsCustomResizer: url("data:image/svg+xml;utf8,%3Csvg width='20' height='20' viewBox='0 0 5.2916665 5.2916666' version='1.1' id='svg5' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs id='defs2' /%3E%3Cg id='layer1'%3E%3Cpath style='fill:%23aaa;stroke:%23aaa;stroke-width:0.264583;stroke-dasharray:none;stroke-opacity:1' d='M 2.8245112,5.0994569 C 4.9396993,2.9842685 4.9396993,2.9842685 4.9396993,2.9842685' id='path1034' /%3E%3Cpath style='fill:%23aaa;stroke:%23aaa;stroke-width:0.264583;stroke-dasharray:none;stroke-opacity:1' d='M 1.515747,5.0406977 C 4.8873304,1.6691138 4.8873304,1.6691138 4.8873304,1.6691138' id='path1034-1' /%3E%3Cpath style='fill:%23aaa;stroke:%23aaa;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1' d='M 0.20657789,5.0909782 C 4.9312207,0.3663348 4.9312207,0.3663348 4.9312207,0.3663348' id='path1034-9' /%3E%3C/g%3E%3C/svg%3E");
32
+ --semanticFormsSearchIcon: url("data:image/svg+xml,%3Csvg%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M15.7955%2015.8111L21%2021M18%2010.5C18%2014.6421%2014.6421%2018%2010.5%2018C6.35786%2018%203%2014.6421%203%2010.5C3%206.35786%206.35786%203%2010.5%203C14.6421%203%2018%206.35786%2018%2010.5Z%22%20stroke%3D%22%23000000%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
27
33
  }
28
34
 
29
35
  @mixin dark-mode {
@@ -42,8 +48,14 @@ form.semanticForms, form.semanticForms.light {
42
48
  --semanticFormsNestedFieldsetBorder: 1px rgba(255, 255, 255, .1) solid;
43
49
  --semanticFormsNestedInputBgColor: rgba(255, 255, 255, .15);
44
50
  --semanticFormsBorder: none;
51
+ --semanticFormsTableTitleColor: #3f3f3f;
52
+ --semanticFormsTableHeaderColor: #4f4f4f;
53
+ --semanticFormsTableOddColor: #3f3f3f;
54
+ --semanticFormsTableEvenColor: #4f4f4f;
55
+ --semanticFormsTableBorder: 1px #656565 solid;
45
56
  --semanticFormsSelectIcon: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23fff'%3E%3Cpolygon stroke='%23fff' points='0,0 100,0 50,50'/%3E%3C/svg%3E");
46
57
  --semanticFormsCustomResizer: url("data:image/svg+xml;utf8,%3Csvg width='20' height='20' viewBox='0 0 5.2916665 5.2916666' version='1.1' id='svg5' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'%3E%3Cdefs id='defs2' /%3E%3Cg id='layer1'%3E%3Cpath style='fill:%23c9c9c9;stroke:%23c9c9c9;stroke-width:0.264583;stroke-dasharray:none;stroke-opacity:1' d='M 2.8245112,5.0994569 C 4.9396993,2.9842685 4.9396993,2.9842685 4.9396993,2.9842685' id='path1034' /%3E%3Cpath style='fill:%23c9c9c9;stroke:%23c9c9c9;stroke-width:0.264583;stroke-dasharray:none;stroke-opacity:1' d='M 1.515747,5.0406977 C 4.8873304,1.6691138 4.8873304,1.6691138 4.8873304,1.6691138' id='path1034-1' /%3E%3Cpath style='fill:%23c9c9c9;stroke:%23c9c9c9;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1' d='M 0.20657789,5.0909782 C 4.9312207,0.3663348 4.9312207,0.3663348 4.9312207,0.3663348' id='path1034-9' /%3E%3C/g%3E%3C/svg%3E");
58
+ --semanticFormsSearchIcon: url("data:image/svg+xml,%3Csvg%20width%3D%2214px%22%20height%3D%2214px%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M15.7955%2015.8111L21%2021M18%2010.5C18%2014.6421%2014.6421%2018%2010.5%2018C6.35786%2018%203%2014.6421%203%2010.5C3%206.35786%206.35786%203%2010.5%203C14.6421%203%2018%206.35786%2018%2010.5Z%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E");
47
59
 
48
60
  input::-webkit-calendar-picker-indicator {
49
61
  filter: invert(100%);
@@ -53,7 +65,7 @@ form.semanticForms, form.semanticForms.light {
53
65
  @media (prefers-color-scheme: dark) {
54
66
  html:not(.light) {
55
67
  body:not(.light) {
56
- form.semanticForms {
68
+ form.semanticForms, table.semanticForms {
57
69
  @include dark-mode;
58
70
  }
59
71
  }
@@ -62,21 +74,27 @@ form.semanticForms, form.semanticForms.light {
62
74
 
63
75
  html:not(.light) {
64
76
  body:not(.light) {
65
- form.semanticForms.dark {
77
+ form.semanticForms.dark, table.semanticForms.dark {
66
78
  @include dark-mode;
67
79
  }
68
80
  }
69
81
  }
70
82
  // #endregion
71
83
 
72
- form.semanticForms, form.semanticForms.light {
84
+ form.semanticForms,
85
+ form.semanticForms.light,
86
+ table.semanticForms,
87
+ table.semanticForms.light {
73
88
  font-family: var(--semanticFormsFontFamily);
74
89
  width: 100%;
75
90
  container-type: inline-size;
76
91
  container-name: semanticForm;
77
- display: flex;
78
- flex-direction: column;
79
- gap: 10px;
92
+
93
+ &:not(table) {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 10px;
97
+ }
80
98
 
81
99
  // style reset
82
100
  fieldset,
@@ -175,14 +193,35 @@ form.semanticForms, form.semanticForms.light {
175
193
 
176
194
  select {
177
195
  cursor: pointer;
178
- // custom dropdown graphic on select element
179
- background-image: var(--semanticFormsSelectIcon);
180
- background-size: 14px;
181
- background-position: calc(100% - 8px) 16px;
182
- background-repeat: no-repeat;
183
- -webkit-appearance: none;
184
- -moz-appearance: none;
185
- appearance: none;
196
+
197
+ &:not([multiple]) {
198
+ // custom dropdown graphic on select element
199
+ background-image: var(--semanticFormsSelectIcon);
200
+ background-size: 14px;
201
+ background-position: calc(100% - 8px) 16px;
202
+ background-repeat: no-repeat;
203
+ -webkit-appearance: none;
204
+ -moz-appearance: none;
205
+ appearance: none;
206
+ }
207
+
208
+ &[multiple] {
209
+ min-height: 38px;
210
+ height: 100px;
211
+ padding: 0 !important;
212
+
213
+ option {
214
+ padding: 6px 20px;
215
+
216
+ &:first-of-type {
217
+ border-top-left-radius: var(--semanticFormsBorderRadius);
218
+ }
219
+
220
+ &:last-of-type {
221
+ border-bottom-left-radius: var(--semanticFormsBorderRadius);
222
+ }
223
+ }
224
+ }
186
225
  }
187
226
 
188
227
  textarea {
@@ -192,9 +231,27 @@ form.semanticForms, form.semanticForms.light {
192
231
  height: 100px;
193
232
  }
194
233
 
195
- // hide native cancel button
196
- input[type="search"]::-webkit-search-cancel-button {
197
- display: none;
234
+ input[type="search"] {
235
+ background: var(--semanticFormsSearchIcon) no-repeat 10px;
236
+ background-color: var(--semanticFormsFormBgColor);
237
+ background-size: 18px;
238
+ padding-left: 32px !important; // shift values to the right
239
+
240
+ & + label.floatLabelFormAnimatedLabel {
241
+ left: 32px !important; // override float label
242
+ }
243
+
244
+ &:focus,
245
+ &:not(:placeholder-shown) {
246
+ & + label.floatLabelFormAnimatedLabel {
247
+ left: 20px !important;
248
+ }
249
+ }
250
+
251
+ // hide native cancel button
252
+ &::-webkit-search-cancel-button {
253
+ display: none;
254
+ }
198
255
  }
199
256
  // #endregion
200
257
 
@@ -253,6 +310,7 @@ form.semanticForms, form.semanticForms.light {
253
310
  padding: 5px 25px;
254
311
  font-size: 100%;
255
312
  line-height: 1;
313
+ user-select: none;
256
314
 
257
315
  &:active {
258
316
  background-image: linear-gradient(to bottom, var(--semanticFormsButtonGradientDark) 0%, var(--semanticFormsButtonGradientLight) 100%);
@@ -362,6 +420,10 @@ form.semanticForms, form.semanticForms.light {
362
420
  margin: 0 2px 2px 0;
363
421
  }
364
422
  }
423
+
424
+ select[multiple] {
425
+ scrollbar-color: var(--semanticFormsScrollbarColor) transparent;
426
+ }
365
427
  }
366
428
 
367
429
  // #region container layout
@@ -375,22 +437,52 @@ form.semanticForms, form.semanticForms.light {
375
437
  dd {
376
438
  position: relative;
377
439
 
440
+ &.checkboxes, &.radios {
441
+ display: flex;
442
+ flex-direction: column;
443
+ gap: 10px;
444
+
445
+ // checkbox subtext
446
+ & > label {
447
+ padding-left: 20px;
448
+ font-size: small;
449
+ color: var(--semanticFormsTextColor);
450
+ }
451
+ }
452
+
378
453
  &:not(.checkboxes, .radios) {
379
454
  gap: 10px;
380
455
  max-width: 100%;
381
456
 
382
- // places all items within the <dd> horizontally
383
- &:not(.singleCheckbox) {
457
+ // places all items within the <dd> vertically
458
+ &:not(.singleCheckbox, .singleRadio) {
384
459
  display: flex;
385
460
  flex-direction: column;
386
461
  }
387
462
 
388
- &.singleCheckbox {
389
- display: flex;
463
+ &.singleCheckbox,
464
+ &.singleRadio {
465
+ display: grid;
466
+ grid-template-columns: 15px 1fr;
390
467
  align-items: center;
468
+ padding-left: 11px;
469
+
470
+ & > :not(input[type='checkbox'], input[type='radio'], label) {
471
+ grid-column: 1 / -1;
472
+ }
391
473
 
392
- & > :not(input[type='checkbox'], label) {
474
+ // single checkbox subtext
475
+ label:nth-of-type(2) {
393
476
  grid-column: 1 / -1;
477
+ padding-left: 20px;
478
+ font-size: small;
479
+ color: var(--semanticFormsTextColor);
480
+ }
481
+ }
482
+
483
+ &.singleRadio {
484
+ input {
485
+ margin: 0;
394
486
  }
395
487
  }
396
488
  }
@@ -468,7 +560,7 @@ form.semanticForms, form.semanticForms.light {
468
560
  max-width: 100%;
469
561
  max-width: 365px;
470
562
 
471
- dd:not(.singleCheckbox, .checkboxes, .radios) {
563
+ dd:not(.singleCheckbox, .singleRadio, .checkboxes, .radios) {
472
564
  input:not([type='submit'], [type='reset'], [type='image']),
473
565
  select,
474
566
  textarea {
@@ -620,6 +712,7 @@ form.semanticForms, form.semanticForms.light {
620
712
  position: relative;
621
713
 
622
714
  label {
715
+ z-index: 2; // ensure the hoverable icon is interactable
623
716
  user-select: auto;
624
717
  color: var(--semanticFormsTextColor);
625
718
  font-size: var(--semanticFormsInputFontSize);
@@ -633,7 +726,7 @@ form.semanticForms, form.semanticForms.light {
633
726
  text-overflow: ellipsis;
634
727
  min-width: 0;
635
728
  width: calc(100% + 20px);
636
-
729
+
637
730
  // TODO: remove this? // &:has(span.help) {
638
731
  // width: 100%;
639
732
  // padding-right: 20px;
@@ -976,22 +1069,22 @@ form.semanticForms, form.semanticForms.light {
976
1069
  dl {
977
1070
  grid-auto-flow: dense;
978
1071
  }
979
-
1072
+
980
1073
  // 1, 4, 7, ...
981
1074
  & > dt:nth-of-type(2n + 1),
982
1075
  & > dd:nth-of-type(2n + 1) {
983
1076
  grid-column: 1;
984
1077
  }
985
-
1078
+
986
1079
  // 2, 5, 8, ...
987
1080
  & > dt:nth-of-type(2n + 2),
988
1081
  & > dd:nth-of-type(2n + 2) {
989
1082
  grid-column: 2;
990
1083
  }
991
-
1084
+
992
1085
  dt {
993
1086
  min-width: var(--semanticFormsInputMinWidth) !important; /* this matches the max-width of js-enabled inputs */
994
-
1087
+
995
1088
  // add required asterisk
996
1089
  &:has(+ dd input:required) {
997
1090
  label::after {
@@ -1000,26 +1093,26 @@ form.semanticForms, form.semanticForms.light {
1000
1093
  }
1001
1094
  }
1002
1095
  }
1003
-
1004
-
1096
+
1097
+
1005
1098
  dd {
1006
1099
  margin-bottom: 10px;
1007
1100
  min-width: var(--semanticFormsInputMinWidth) !important; /* this matches the max-width of js-enabled inputs */
1008
-
1101
+
1009
1102
  // nudge single checkboxes to match checkbox groups
1010
1103
  &:not(.checkboxes) {
1011
1104
  input[type=checkbox] {
1012
1105
  margin-left: 10px;
1013
1106
  }
1014
1107
  }
1015
-
1108
+
1016
1109
  // remove "x" padding
1017
1110
  input:not([type=submit], [type=reset], [type=image], [type=checkbox], [type=radio], [type=range]),
1018
1111
  select,
1019
1112
  textarea {
1020
1113
  padding-right: 6px;
1021
1114
  }
1022
-
1115
+
1023
1116
  // subtext
1024
1117
  label {
1025
1118
  margin-top: -5px;
@@ -1028,11 +1121,66 @@ form.semanticForms, form.semanticForms.light {
1028
1121
  }
1029
1122
  }
1030
1123
  }
1031
-
1124
+
1032
1125
  .checkboxes ul,
1033
1126
  .radios ul {
1034
- margin-top: calc(var(--semanticFormsInputTopMargin) / 2);
1127
+ margin-top: var(--semanticFormsInputTopMargin);
1035
1128
  }
1036
1129
  }
1037
1130
  // #endregion
1038
1131
  }
1132
+
1133
+ // #region tables
1134
+
1135
+ .semanticForms table,
1136
+ table.semanticForms {
1137
+ table-layout: fixed;
1138
+ width: 100%;
1139
+ border-spacing: 0;
1140
+ border: var(--semanticFormsBorder);
1141
+ border-radius: 10px;
1142
+ margin-bottom: 15px;
1143
+ overflow: hidden;
1144
+
1145
+ tr.title th {
1146
+ background-color: var(--semanticFormsTableTitleColor);
1147
+ }
1148
+
1149
+ th {
1150
+ background-color: var(--semanticFormsTableHeaderColor);
1151
+ font-weight: bold;
1152
+ }
1153
+
1154
+ tr:nth-child(odd) td {
1155
+ background-color: var(--semanticFormsTableOddColor);
1156
+ }
1157
+
1158
+ tr:nth-child(even) td {
1159
+ background-color: var(--semanticFormsTableEvenColor);
1160
+ }
1161
+
1162
+ td, th {
1163
+ border-right: var(--semanticFormsTableBorder);
1164
+ border-bottom: var(--semanticFormsTableBorder);
1165
+ padding: 7px 10px;
1166
+ }
1167
+
1168
+ th:last-of-type, td:last-of-type {
1169
+ border-right: none;
1170
+ }
1171
+
1172
+ tbody {
1173
+ tr:last-of-type {
1174
+ th, td {
1175
+ border-bottom: none;
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ // adjust styling of button menus in tables
1181
+ menu:has(li > input, li > label, li > button, li > select, li > textarea) {
1182
+ width: max-content;
1183
+ }
1184
+
1185
+ }
1186
+ // #endregion