semantic-forms 5.3.5 → 5.3.7

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/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.3.5",
11
+ "version": "5.3.7",
12
12
  "files": [
13
13
  "dist",
14
14
  "semanticForms.scss",
@@ -26,30 +26,34 @@
26
26
  },
27
27
  "homepage": "https://rooseveltframework.org/docs/semantic-forms/latest",
28
28
  "license": "CC-BY-4.0",
29
+ "engines": {
30
+ "node": ">=22.0.0"
31
+ },
29
32
  "main": "semanticForms.js",
30
33
  "readmeFilename": "README.md",
31
34
  "devDependencies": {
32
35
  "@jsdevtools/coverage-istanbul-loader": "3.0.5",
33
- "@playwright/test": "1.58.2",
34
- "eslint": "10.0.0",
35
- "eslint-plugin-html": "8.1.4",
36
- "express": "5.2.1",
37
- "nodemon": "3.1.11",
38
- "nyc": "17.1.0",
39
- "sass": "1.97.3",
40
- "http-server": "14.1.1",
36
+ "@playwright/test": "1.63.0",
37
+ "eslint-plugin-html": "8.2.0",
38
+ "nyc": "18.0.0",
39
+ "sass": "1.104.1",
41
40
  "standard": "17.1.2",
42
- "webpack-cli": "6.0.1"
41
+ "terser-webpack-plugin": "5.6.1",
42
+ "webpack": "5.111.0",
43
+ "webpack-cli": "7.2.3"
43
44
  },
44
45
  "scripts": {
46
+ "prepare": "npm run build",
45
47
  "build": "webpack && sass semanticForms.scss dist/semantic-forms.css",
46
48
  "watch": "webpack && sass --watch semanticForms.scss dist/semantic-forms.css",
47
- "d": "nodemon dev-server.js --ext js,scss",
48
- "dev": "nodemon dev-server.js --ext js,scss",
49
- "development": "nodemon dev-server.js --ext js,scss",
49
+ "d": "node --watch-path=semanticForms.js --watch-path=lib --watch-path=semanticForms.scss --watch-path=styles --watch-path=docs/statics dev-server.js",
50
+ "dev": "node --watch-path=semanticForms.js --watch-path=lib --watch-path=semanticForms.scss --watch-path=styles --watch-path=docs/statics dev-server.js",
51
+ "development": "node --watch-path=semanticForms.js --watch-path=lib --watch-path=semanticForms.scss --watch-path=styles --watch-path=docs/statics dev-server.js",
50
52
  "lint": "standard && standard --plugin html docs/statics/pages/*.html",
51
53
  "lint-fix": "standard --fix && standard --plugin html docs/statics/pages/*.html --fix",
52
54
  "test": "npm run build && playwright test",
55
+ "test-visual": "playwright test --config=playwright.visual.config.js",
56
+ "test-visual-docker": "node visual-tests.js",
53
57
  "coverage": "webpack --env coverage && sass semanticForms.scss dist/semantic-forms.css && nyc playwright test && npm run build"
54
58
  },
55
59
  "standard": {
@@ -58,6 +62,16 @@
58
62
  "docs/public"
59
63
  ]
60
64
  },
65
+ "nyc": {
66
+ "include": [
67
+ "semanticForms.js",
68
+ "lib/**/*.js"
69
+ ],
70
+ "reporter": [
71
+ "text",
72
+ "html"
73
+ ]
74
+ },
61
75
  "repository": {
62
76
  "type": "git",
63
77
  "url": "git://github.com/rooseveltframework/semantic-forms.git"
package/semanticForms.js CHANGED
@@ -1,7 +1,15 @@
1
1
  const { createKeyboardShortcut, shortcutListener } = require('./lib/keyboardShortcuts.js')
2
2
  const { enhanceInput, handleUndoRedo } = require('./lib/inputEnhancements.js')
3
+ const { enhanceTabs } = require('./lib/tabs.js')
4
+ const { setInkOffset } = require('./lib/helpers.js')
3
5
 
4
- const semanticForms = () => {
6
+ // one observer per tree that has been handed to semanticForms, so that a shadow root gets its own without replacing the document's
7
+ const observers = new WeakMap()
8
+
9
+ // progressively enhances every semanticForms form in a tree
10
+ //
11
+ // root: document or shadow root to search; defaults to the main document
12
+ const semanticForms = (root = document) => {
5
13
  // do some feature detection so none of the JS executes if the browser is too old
6
14
  if (typeof document.getElementsByClassName !== 'function' || typeof document.querySelector !== 'function' || !document.body.classList || !window.MutationObserver) {
7
15
  console.warn('semantic-forms was loaded into an unsupported browser and will not execute.')
@@ -13,12 +21,15 @@ const semanticForms = () => {
13
21
  document.addEventListener('keydown', (e) => shortcutListener(e, keyboardShortcuts))
14
22
 
15
23
  // progressively enhance form elements that have the semanticForms class
16
- const forms = document.querySelectorAll('form.semanticForms:not(.semanticFormsActive), table.semanticForms:not(.semanticFormsActive)')
24
+ const forms = root.querySelectorAll('form.semanticForms:not(.semanticFormsActive), table.semanticForms:not(.semanticFormsActive)')
17
25
 
18
26
  for (const form of forms) {
19
27
  form.classList.add('semanticFormsActive')
20
28
  if (form.classList.contains('lowFlow')) continue
21
29
 
30
+ // how far this font's letters sit from the middle of their own line box, which the stylesheet subtracts wherever it centers text. it is measured per form rather than per field, so that a form holding nothing but buttons is corrected too
31
+ setInkOffset(form, form)
32
+
22
33
  // update each input in the semantic form
23
34
  const inputs = Array.from(form.querySelectorAll('input, textarea, select'))
24
35
  for (const input of inputs) {
@@ -30,32 +41,46 @@ const semanticForms = () => {
30
41
  keyboardShortcuts.push(shortcut)
31
42
  }
32
43
  }
44
+
45
+ // done after the inputs so that anything sizing itself measures while it is still on screen
46
+ enhanceTabs(form)
33
47
  }
34
48
 
35
49
  // prevents multiple listeners
36
50
  document.removeEventListener('keydown', handleUndoRedo)
37
51
  document.addEventListener('keydown', handleUndoRedo)
38
52
 
39
- // monitor changes to the DOM and enhance new semanticForms forms that get added
40
- if (!window.semanticFormsObserver) {
41
- window.semanticFormsObserver = new window.MutationObserver(mutations => {
53
+ // monitor changes to this tree and enhance new semanticForms forms that get added to it
54
+ if (!observers.has(root)) {
55
+ const observer = new window.MutationObserver(mutations => {
42
56
  let stop = false
43
57
  for (const mutation of mutations) {
44
58
  for (const node of mutation.addedNodes) {
45
59
  if (node.nodeName === 'FORM' || node?.querySelector?.('form')) {
46
- semanticForms()
60
+ semanticForms(root)
47
61
  stop = true
48
62
  }
49
63
  }
50
64
  if (stop) break
51
65
  }
52
66
  })
53
- window.semanticFormsObserver.observe(document.body, { attributes: false, childList: true, characterData: false, subtree: true })
67
+
68
+ // a shadow root is observed directly; the document is observed through its body
69
+ observer.observe(root === document ? document.body : root, { attributes: false, childList: true, characterData: false, subtree: true })
70
+ observers.set(root, observer)
71
+
72
+ // long standing name for the document's observer, kept for anything already using it
73
+ if (root === document) window.semanticFormsObserver = observer
54
74
  }
55
75
 
56
76
  semanticForms.reinitialize = form => {
77
+ // with no form given this just rescans for forms that are not active yet
78
+ if (!form) return semanticForms(root)
79
+
57
80
  form.classList.remove('semanticFormsActive')
58
- semanticForms()
81
+
82
+ // the form knows which tree it belongs to, so a form in a shadow root reinitializes there
83
+ semanticForms(form.getRootNode())
59
84
  }
60
85
  }
61
86
 
@@ -2,6 +2,7 @@
2
2
  @use 'styles/inputs.scss';
3
3
  @use 'styles/labels.scss';
4
4
  @use 'styles/colspan.scss';
5
+ @use 'styles/tabs.scss';
5
6
  @use 'styles/lowFlow.scss';
6
7
 
7
8
  .semanticForms {
@@ -66,7 +67,8 @@
66
67
  margin-bottom: 10px;
67
68
  line-height: 1;
68
69
  position: absolute;
69
- top: 10px;
70
+ // the ink offset comes off here as it does elsewhere, so the words rather than the box holding them line up
71
+ top: calc(10px - var(--semanticFormsTextInkOffset, 0px));
70
72
  left: 22px;
71
73
  transition: top 250ms;
72
74
 
@@ -82,8 +84,10 @@
82
84
  border-style: solid;
83
85
  border-color: transparent transparent transparent var(--semanticFormsTextColor);
84
86
  position: absolute;
85
- top: 0;
86
- left: -10px;
87
+ // centered on the line of text beside it: the arrow is 0.8rem tall and the line is 1em, so half the difference puts it in the middle, less the measured ink offset because the letters do not sit in the middle of their own line either. it was pinned to the top of the line before
88
+ top: calc((1em - 0.8rem) / 2 + var(--semanticFormsFieldTextInkOffset, 0px));
89
+ // far enough from the words to read as a separate mark rather than part of the first letter
90
+ left: -14px;
87
91
  transform: rotate(0);
88
92
  transform-origin: .2rem 50%;
89
93
  transition: 250ms transform ease;
@@ -135,7 +139,7 @@
135
139
 
136
140
  dl {
137
141
  display: grid;
138
- grid-template-columns: repeat(auto-fit, minmax(var(--semanticFormsInputMinWidth), 1fr));
142
+ grid-template-columns: repeat(auto-fit, minmax(var(--semanticFormsInputMinWidth), var(--semanticFormsInputColumnMaxWidth)));
139
143
  gap: 10px;
140
144
  margin: 0;
141
145
 
@@ -183,6 +187,11 @@
183
187
  padding-left: 11px;
184
188
  gap: 5px 10px;
185
189
 
190
+ // a switch is wider than a checkbox, so it needs a wider column to sit in
191
+ &:has(input[type=checkbox][switch]) {
192
+ grid-template-columns: 34px 1fr;
193
+ }
194
+
186
195
  // anything that is not the radio/checkbox and its label
187
196
  & > :not(input[type='checkbox'], input[type='radio'], label) {
188
197
  grid-column: 1 / -1;
@@ -257,7 +266,8 @@
257
266
  grid-column: 2 / 3;
258
267
  width: 100%;
259
268
  max-width: 100%;
260
- padding: 10px; // reduce default padding to allow more button text to show
269
+ // reduced from the default so more of the button's text fits. the vertical halves carry the measured ink offset, as they do on every other control, so the words sit in the middle of the button rather than the box holding them sitting there
270
+ padding: calc(10px - var(--semanticFormsFieldTextInkOffset, 0px)) 10px calc(10px + var(--semanticFormsFieldTextInkOffset, 0px));
261
271
  height: var(--semanticFormsInputHeight);
262
272
  text-overflow: ellipsis;
263
273
  white-space: nowrap;