semantic-forms 5.3.5 → 5.3.6

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.6",
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.110.3",
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
 
@@ -83,7 +85,8 @@
83
85
  border-color: transparent transparent transparent var(--semanticFormsTextColor);
84
86
  position: absolute;
85
87
  top: 0;
86
- left: -10px;
88
+ // far enough from the words to read as a separate mark rather than part of the first letter
89
+ left: -14px;
87
90
  transform: rotate(0);
88
91
  transform-origin: .2rem 50%;
89
92
  transition: 250ms transform ease;
@@ -135,7 +138,7 @@
135
138
 
136
139
  dl {
137
140
  display: grid;
138
- grid-template-columns: repeat(auto-fit, minmax(var(--semanticFormsInputMinWidth), 1fr));
141
+ grid-template-columns: repeat(auto-fit, minmax(var(--semanticFormsInputMinWidth), var(--semanticFormsInputColumnMaxWidth)));
139
142
  gap: 10px;
140
143
  margin: 0;
141
144
 
@@ -183,6 +186,11 @@
183
186
  padding-left: 11px;
184
187
  gap: 5px 10px;
185
188
 
189
+ // a switch is wider than a checkbox, so it needs a wider column to sit in
190
+ &:has(input[type=checkbox][switch]) {
191
+ grid-template-columns: 34px 1fr;
192
+ }
193
+
186
194
  // anything that is not the radio/checkbox and its label
187
195
  & > :not(input[type='checkbox'], input[type='radio'], label) {
188
196
  grid-column: 1 / -1;
@@ -257,7 +265,8 @@
257
265
  grid-column: 2 / 3;
258
266
  width: 100%;
259
267
  max-width: 100%;
260
- padding: 10px; // reduce default padding to allow more button text to show
268
+ // 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
269
+ padding: calc(10px - var(--semanticFormsFieldTextInkOffset, 0px)) 10px calc(10px + var(--semanticFormsFieldTextInkOffset, 0px));
261
270
  height: var(--semanticFormsInputHeight);
262
271
  text-overflow: ellipsis;
263
272
  white-space: nowrap;