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/CHANGELOG.md +20 -0
- package/CONTRIBUTING.md +39 -7
- package/dist/semantic-forms.cjs +119 -52
- package/dist/semantic-forms.cjs.map +1 -1
- package/dist/semantic-forms.css +497 -177
- package/dist/semantic-forms.css.map +1 -1
- package/dist/semantic-forms.js +153 -80
- 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 +120 -53
- package/dist/semantic-forms.mjs.map +1 -1
- package/docs/statics/css/semantic-forms-styles.scss +14 -1
- package/docs/statics/js/semantic-forms-demo.js +16 -0
- package/docs/statics/js/semantic-forms-main.js +3 -2
- package/docs/statics/pages/buttons.html +98 -0
- package/docs/statics/pages/checkboxes.html +130 -0
- package/docs/statics/pages/fullDemo.html +1078 -821
- package/docs/statics/pages/inputs.html +384 -0
- package/docs/statics/pages/layout.html +521 -0
- package/docs/statics/pages/layouts/main.html +9 -1
- package/docs/statics/pages/theming.html +39 -0
- package/docs/statics/pages/usage.html +17 -799
- package/package.json +27 -13
- package/semanticForms.js +33 -8
- package/semanticForms.scss +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 5.3.6
|
|
2
|
+
|
|
3
|
+
- Added support for the HTML `switch` attribute on checkboxes. Browsers with native support render their own switch; elsewhere Semantic Forms draws one, applies the `switch` ARIA role, and supports dragging the thumb to toggle it.
|
|
4
|
+
- Added `col-1` through `col-5` classes for placing a field in a particular grid column, which can be combined with `colspan-#`. Like spans, positions fall back to normal flow when the form narrows to fewer columns than the class asks for.
|
|
5
|
+
- Added a `--semanticFormsInputColumnMaxWidth` variable, defaulting to `1fr`, which sets the widest an input column in the layout grid may grow. `--semanticFormsInputMaxWidth` is unchanged and still caps the field itself within its column.
|
|
6
|
+
- Added tab support. An element with the `tabs` class holding one `<fieldset>` per tab becomes a tab strip, titled by each fieldset's `<legend>`, with the `tab` and `tabpanel` roles and arrow key navigation. Without JavaScript the fieldsets stay stacked and readable.
|
|
7
|
+
- Added a `data-display-value` attribute for range inputs, matching how every other per-field option is set. The `displayValue` class it replaces still works.
|
|
8
|
+
- Fixed lowFlow single checkbox and radio styling leaking. The rule matched any ancestor `<div>` containing a checkbox rather than the one wrapping it, so every field inside such a container lost the space above its label.
|
|
9
|
+
- Fixed fields being reordered on enhancement. Every enhanced field was moved to the end of its `<dl>`, which pushed anything the enhancer leaves alone, such as a `<dd>` containing only a button, to the top. Fields now stay where they were written.
|
|
10
|
+
- Fixed the float label, the clear button and the keyboard shortcut hint sitting slightly off center on their fields. Each was placed with a fixed offset that only centered the one font size it was written for. They are now positioned from the field's own height, so they stay centered when `--semanticFormsInputHeight` or `--semanticFormsInputFontSize` is overridden.
|
|
11
|
+
- Fixed the float label's text still reading low even once its box was centered. Browsers position a line of text by the font's ascent and descent, which are rarely symmetrical, so the letters sit slightly off center inside the box holding them by an amount that differs with the font the page resolves. Semantic Forms now measures that for whichever font is in use and corrects for it, to the nearest whole pixel the display can draw.
|
|
12
|
+
- Fixed the focus highlight rendering thinner on fields sitting flush against the edge of their form. It is now drawn inside the field, where the form's overflow clipping cannot cut it off.
|
|
13
|
+
- `semanticForms()` now takes an optional document or shadow root to search, so forms inside a shadow root can be enhanced with `semanticForms(myElement.shadowRoot)`. Each tree is watched separately, and `reinitialize()` works out which tree a form belongs to. Labels are now looked up in the form's own tree rather than the document, which is what previously stopped fields in a shadow root from being enhanced at all.
|
|
14
|
+
- Fixed inputs with the `readonly` or `disabled` attribute being given a clear button, which could empty a field the visitor is not meant to be able to change. A field made readonly after the enhancement has run has its clear button hidden by the stylesheet instead.
|
|
15
|
+
- Fixed an error thrown when undo was pressed while a field with no clear button was focused, such as a range input.
|
|
16
|
+
- Fixed undo and redo duplicating a field's contents. The browser's own undo ran in addition to the library's, applying the edit it reverses on top of the value the library had already restored.
|
|
17
|
+
- Fixed the library styling markup outside its own forms. A bare `fieldset` selector gave every fieldset on the host page a container context, which then applied the responsive grid to any `<dl>` inside any fieldset, and the `.checkboxes`/`.radios` list rule was not scoped to the form either.
|
|
18
|
+
- Improved docs.
|
|
19
|
+
- Updated dependencies.
|
|
20
|
+
|
|
1
21
|
## 5.3.5
|
|
2
22
|
|
|
3
23
|
- Fixed visual issues that occurred on standalone and nested `<details>` elements.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# How to contribute
|
|
1
|
+
# How to contribute
|
|
2
2
|
|
|
3
3
|
## Coding
|
|
4
4
|
|
|
@@ -7,24 +7,56 @@
|
|
|
7
7
|
- Make your changes. If you want to alter the CSS, do the changes in the `.scss` files.
|
|
8
8
|
- `npm run build`. The build step compiles the SCSS file into CSS.
|
|
9
9
|
- You can also run `npm run watch` automatically compiles the CSS file after a change is detected in the SCSS file.
|
|
10
|
-
- Test your changes by
|
|
11
|
-
- If you want to test your work on an actual HTTP server, run `npm run dev` or `npm run d`, and then visit http://localhost:3000/test/semanticForms
|
|
10
|
+
- Test your changes by running `npm run dev` (or `npm run d`) and visiting http://localhost:6588/fullDemo.html. The dev server rebuilds the library and the docs site as you edit them.
|
|
12
11
|
- Commit, push, open pull request.
|
|
13
12
|
|
|
14
13
|
## Before opening a pull request
|
|
15
14
|
|
|
16
15
|
- Be sure all tests pass: `npm t`.
|
|
17
|
-
- Ensure
|
|
16
|
+
- Ensure good test coverage and write new tests if necessary: `npm run coverage`.
|
|
17
|
+
- Run visual regression tests too (see below).
|
|
18
|
+
- If your change affects how anything looks, refresh the visual baselines (see below).
|
|
18
19
|
- Add your changes to `CHANGELOG.md`.
|
|
19
20
|
|
|
21
|
+
### Visual regression tests
|
|
22
|
+
|
|
23
|
+
These compare screenshots of the demo page against committed baselines. Screenshots depend on the operating system, the browser and the installed fonts, so they run inside the official Playwright container, which pins that environment for everyone. They stay out of `npm t` so an ordinary test run does not need Docker.
|
|
24
|
+
|
|
25
|
+
To run them:
|
|
26
|
+
|
|
27
|
+
Do not run `npm run test-visual` directly. It renders with your fonts rather than the container's and reports differences that are not real.
|
|
28
|
+
|
|
29
|
+
Install [Docker](https://www.docker.com) and [Git LFS](https://git-lfs.com), then `npm ci`, then:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run test-visual-docker
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A failed comparison writes `-expected.png`, `-actual.png` and `-diff.png` into `test-results`, which CI uploads as the `visual-regression-report` artifact. Look at the diff: if the change is one you meant to make, refresh the baselines and commit the images it writes to `test/visual.spec.js-snapshots`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm run test-visual-docker -- --update-snapshots
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If the visual diff is not a diff you meant to make, then you have found a regression.
|
|
42
|
+
|
|
43
|
+
#### Updating the Playwright dependency
|
|
44
|
+
|
|
45
|
+
A Playwright dependency update may cause the visual diff tests to fail, even though there is no actual bug to fix, due to subtle changes in how the browser renders things. When this happens, regenerate the baselines alongside the dependency update:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run test-visual-docker -- --update-snapshots
|
|
49
|
+
```
|
|
50
|
+
|
|
20
51
|
## Release process
|
|
21
52
|
|
|
22
|
-
If you are a maintainer
|
|
53
|
+
If you are a maintainer, please follow the following release procedure:
|
|
23
54
|
|
|
24
|
-
- Merge all desired pull requests into
|
|
55
|
+
- Merge all desired pull requests into main.
|
|
25
56
|
- Bump `package.json` to a new version and run `npm i` to generate a new `package-lock.json`.
|
|
26
|
-
-
|
|
57
|
+
- Add new version to CHANGELOG.
|
|
27
58
|
- Paste contents of CHANGELOG into new version commit.
|
|
28
59
|
- Open and merge a pull request with those changes.
|
|
29
60
|
- Tag the merge commit as the a new release version number.
|
|
30
61
|
- Publish commit to npm.
|
|
62
|
+
- Submit a pull request to the Roosevelt website [following the instructions here](https://github.com/rooseveltframework/roosevelt-website/blob/main/CONTRIBUTING.md).
|
package/dist/semantic-forms.cjs
CHANGED
|
@@ -1,36 +1,62 @@
|
|
|
1
1
|
(function(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define("semanticForms",[],factory);else if(typeof exports==="object")exports["semanticForms"]=factory();else root["semanticForms"]=factory()})(this,()=>/******/(()=>{// webpackBootstrap
|
|
2
2
|
/******/var __webpack_modules__={
|
|
3
|
-
/***/"./lib/helpers.js"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
/***/"./lib/helpers.js"(module){
|
|
4
|
+
// uses the navigator to best determine the client's operating system
|
|
5
|
+
// returns one of `mac`, `windows`, or `linux`
|
|
6
|
+
const getOS=()=>{const userAgent=window.navigator.userAgent;const platform=window.navigator.platform;let os=null;if(platform.includes("Win"))os="windows";else if(platform.includes("Mac")||/iPhone|iPad|iPod/.test(userAgent))os="mac";else if(platform.includes("Linux")||/Android/.test(userAgent))os="linux";return os};
|
|
7
|
+
// places an element immediately after another element
|
|
8
|
+
|
|
9
|
+
// newNode: the element being placed after the reference node
|
|
10
|
+
// referenceNode: the element the new node is placed after
|
|
11
|
+
const insertAfter=(newNode,referenceNode)=>{if(referenceNode.nextSibling)referenceNode.parentNode.insertBefore(newNode,referenceNode.nextSibling);else referenceNode.parentNode.appendChild(newNode)};
|
|
12
|
+
// works out how far a font's visible text sits from the middle of its own line box
|
|
13
|
+
|
|
14
|
+
// browsers place a line of text by the font's ascent and descent, which are rarely symmetrical, so the letters themselves end up slightly above or below the middle of the box holding them. that is invisible at the half pixel it usually amounts to, until a display without a high pixel density rounds it up to a whole one. the shift is the same for every element using the font, so it is measured once per font and reused
|
|
15
|
+
|
|
16
|
+
// returns the number of pixels the text sits below the middle of its box, negative when it sits above
|
|
17
|
+
const inkOffsets=new Map;const getInkOffset=(fontStyle,fontWeight,fontSize,fontFamily)=>{const key=`${fontStyle} ${fontWeight} ${fontSize} ${fontFamily}`;if(inkOffsets.has(key))return inkOffsets.get(key);
|
|
18
|
+
// the ascent is where the baseline falls in a line box, which an inline-block aligned to the baseline reports directly
|
|
19
|
+
const probe=document.createElement("div");probe.style.cssText=`position:absolute;left:-9999px;top:0;white-space:nowrap;visibility:hidden;line-height:normal;padding:0;border:0;margin:0;font-style:${fontStyle};font-weight:${fontWeight};font-size:${fontSize};font-family:${fontFamily}`;const marker=document.createElement("span");marker.style.cssText="display:inline-block;width:0;height:0;vertical-align:baseline";probe.append("Hxn",marker);document.body.append(probe);const lineBox=probe.getBoundingClientRect();const ascent=marker.getBoundingClientRect().top-lineBox.top;const descent=lineBox.height-ascent;probe.remove();
|
|
20
|
+
// the cap height is where the letters actually start, which only the drawn pixels can say. drawing it large keeps the answer accurate to a fraction of a pixel
|
|
21
|
+
const scale=8;const size=parseFloat(fontSize);const canvas=document.createElement("canvas");canvas.width=Math.ceil(size*scale*4);canvas.height=Math.ceil(size*scale*3);const context=canvas.getContext("2d",{willReadFrequently:true});context.fillStyle="#fff";context.fillRect(0,0,canvas.width,canvas.height);context.fillStyle="#000";context.font=`${fontStyle} ${fontWeight} ${size*scale}px ${fontFamily}`;context.textBaseline="alphabetic";const baseline=Math.round(size*scale*2);context.fillText("Hxn",10,baseline);const pixels=context.getImageData(0,0,canvas.width,canvas.height).data;let inkTop=null;for(let y=0;y<canvas.height&&inkTop===null;y++)for(let x=0;x<canvas.width;x++)if(pixels[(y*canvas.width+x)*4]<128){inkTop=y;break}if(inkTop===null){const none={offset:0,fieldOffset:0};inkOffsets.set(key,none);return none}const capHeight=(baseline-inkTop)/scale;
|
|
22
|
+
// rounded to whole device pixels, because browsers put a line of text on the pixel grid rather than at the fraction of one asked for. a correction finer than that either does nothing or moves the letters a whole pixel, depending on which side of the halfway mark the sum lands
|
|
23
|
+
|
|
24
|
+
// two roundings come out of this, because a measurement landing exactly halfway can go either way and the browser does not send every kind of text the same way. our own labels follow the ordinary rounding; the text a field draws inside itself, and the text on a button, follow the one that rounds a half away from zero. they differ only for a measurement of exactly minus a half, and agree everywhere else
|
|
25
|
+
const ratio=window.devicePixelRatio||1;const exact=((ascent-descent)/2-capHeight/2)*ratio;const offset=Math.round(exact)/ratio;const fieldOffset=Math.sign(exact)*Math.round(Math.abs(exact))/ratio;const pair={offset,fieldOffset};inkOffsets.set(key,pair);return pair};
|
|
26
|
+
// puts the measured offset on the form as a custom property, for the stylesheet to subtract when it centers the float label on its field
|
|
27
|
+
const applyInkOffset=(form,label)=>{if(!label.isConnected)return;const style=window.getComputedStyle(label);const{offset,fieldOffset}=getInkOffset(style.fontStyle,style.fontWeight,style.fontSize,style.fontFamily);form.style.setProperty("--semanticFormsTextInkOffset",`${offset}px`);form.style.setProperty("--semanticFormsFieldTextInkOffset",`${fieldOffset}px`)};
|
|
28
|
+
// a web font that is still downloading when a form is enhanced is measured in whichever font the page falls back to, and the letters move once the real one arrives. the forms measured so far are remembered so they can be measured again at that point, which the browser reports once for the page rather than once per font
|
|
29
|
+
const measured=new Map;let awaitingFonts=false;const setInkOffset=(form,label)=>{measured.set(form,label);applyInkOffset(form,label);if(awaitingFonts||!document.fonts)return;awaitingFonts=true;document.fonts.ready.then(()=>{
|
|
30
|
+
// the cache is keyed by what the font is called, which does not change when the file behind it finally loads
|
|
31
|
+
inkOffsets.clear();for(const[form,label]of measured)applyInkOffset(form,label);measured.clear()})};module.exports={getOS,insertAfter,setInkOffset}
|
|
18
32
|
/***/},
|
|
19
|
-
/***/"./lib/inputEnhancements.js":
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
/***/"./lib/inputEnhancements.js"(module,__unused_webpack_exports,__webpack_require__){const{insertAfter}=__webpack_require__(/*! ./helpers.js */"./lib/helpers.js");const icons={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>',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>',helpIcon:'<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>'};const lookups={nodeName:["TEXTAREA","SELECT"],inputType:["checkbox","color","date","datetime-local","email","file","image","month","number","password","radio","range","search","tel","text","time","url","week"]};let lastFocusedInput;let lastClearFieldPressed;
|
|
34
|
+
// browsers that implement the switch attribute expose it on the input prototype, render the control as a switch and report the switch role themselves, so nothing below is needed there
|
|
35
|
+
const nativeSwitchSupport="switch"in window.HTMLInputElement.prototype;
|
|
36
|
+
// lets a polyfilled switch be toggled by dragging its thumb, which is what the native control does. a pointer that never travels far enough is left alone so it still lands as a click.
|
|
37
|
+
const enableSwitchDragging=input=>{input.addEventListener("pointerdown",event=>{if(input.disabled)return;const startX=event.clientX;const rightToLeft=window.getComputedStyle(input).direction==="rtl";
|
|
38
|
+
// half the track is the distance the thumb travels between the two positions
|
|
39
|
+
const travel=input.clientWidth/2||1;const wasChecked=input.checked;let dragging=false;input.setPointerCapture(event.pointerId);const drag=moveEvent=>{const distance=rightToLeft?startX-moveEvent.clientX:moveEvent.clientX-startX;if(!dragging&&Math.abs(distance)<=5)return;// still within a click
|
|
40
|
+
dragging=true;moveEvent.preventDefault();// don't select or scroll while dragging
|
|
41
|
+
input.checked=(wasChecked?1:0)+distance/travel>.5};const drop=upEvent=>{input.releasePointerCapture(upEvent.pointerId);input.removeEventListener("pointermove",drag);input.removeEventListener("pointerup",drop);input.removeEventListener("pointercancel",drop);if(!dragging)return;
|
|
42
|
+
// the click that follows the drag would otherwise put the switch back where it started
|
|
43
|
+
input.addEventListener("click",clickEvent=>clickEvent.preventDefault(),{once:true,capture:true});if(input.checked!==wasChecked){input.dispatchEvent(new Event("input",{bubbles:true}));input.dispatchEvent(new Event("change",{bubbles:true}))}};input.addEventListener("pointermove",drag);input.addEventListener("pointerup",drop);input.addEventListener("pointercancel",drop)})};const enhanceInput=(input,form)=>{
|
|
24
44
|
// ignore input if it has previously been formatted
|
|
25
45
|
if(input.classList.contains("semanticform")||!input.id)return;const type=input.getAttribute("type");if(!lookups.nodeName.includes(input.nodeName)&&!lookups.inputType.includes(type))return;
|
|
26
46
|
// recursively find <dl> element
|
|
27
|
-
let dl=input.parentNode;while(dl&&dl.nodeName!=="DL")dl=dl.parentNode;if(!dl)return;if(!dl.classList.contains("floatLabelForm"))dl.classList.add("floatLabelForm");
|
|
47
|
+
let dl=input.parentNode;while(dl&&dl.nodeName!=="DL")dl=dl.parentNode;if(!dl)return;if(!dl.classList.contains("floatLabelForm"))dl.classList.add("floatLabelForm");
|
|
48
|
+
// the label is looked up in the input's own tree rather than the document, so that a form inside a shadow root finds its labels instead of coming up empty
|
|
49
|
+
const tree=input.getRootNode();const label=input.parentNode.parentNode.id&&(type==="checkbox"||type==="radio")?tree.querySelector("label[data-for="+input.parentNode.parentNode.id.replace(/\./g,"\\.")+"]"):tree.querySelector("label[for="+input.id.replace(/\./g,"\\.")+"]");if(!label){console.error(`semantic-forms: Found an input (${input.id||input.getAttribute("name")}) without a properly associated label. Make sure there is a label for it and the label has a matching "for" attribute.`);return}input.classList.add("semanticform");
|
|
50
|
+
// an input outside a <dd> is reported by the checks further down, so tolerate it here
|
|
51
|
+
const isWrapped=input.closest("dd")?.parentElement.nodeName==="DIV";
|
|
28
52
|
// #region create labels
|
|
29
53
|
const newLabel=document.createElement("label");newLabel.className="floatLabelFormAnimatedLabel";if(type==="checkbox"||type==="radio"){const div=isWrapped?input.closest("dd").parentElement:document.createElement("div");const dt=label.closest("dt");const dd=input.closest("dd");if(!dt||!dd){console.error(`semantic-forms: Found an input (${input.id||input.getAttribute("name")}) that is not inside a <dd> element.`);return}if(dd.firstChild.nodeName!=="LABEL"){if(type==="checkbox"&&input.parentNode.nodeName==="DD"){newLabel.setAttribute("for",input.id);input.parentNode.classList.add("singleCheckbox");newLabel.className="";label.setAttribute("hidden","hidden");insertAfter(newLabel,input)}if(type==="radio"&&input.parentNode.nodeName==="DD"){newLabel.setAttribute("for",input.id);input.parentNode.classList.add("singleRadio");newLabel.className="";label.setAttribute("hidden","hidden");insertAfter(newLabel,input)}newLabel.innerHTML=label.innerHTML;if(label.hasAttribute("title")&&label.getAttribute("data-show-help-icon")!==null&&!label.querySelector("span.help")){const text=label.getAttribute("title");label.innerHTML+=` <span title="${text}" class="help">${icons.helpIcon}</span>`;newLabel.innerHTML+=` <span title="${text}" class="help">${icons.helpIcon}</span>`}if(dd.querySelector(":required")&&label.getAttribute("data-no-asterisk")===null&&!label.querySelector("span.required")){const text=label.getAttribute("data-asterisk-text")||"This field is required.";label.innerHTML+=` <span title="${text}" class="required">*</span>`;newLabel.innerHTML+=` <span title="${text}" class="required">*</span>`}if(!dd.querySelector("label"))dd.append(newLabel)}
|
|
30
54
|
// if the user did not wrap the input in a div, do it for them
|
|
31
|
-
if(!isWrapped){
|
|
55
|
+
if(!isWrapped){
|
|
56
|
+
// the wrapper goes where the <dt> was, so the field keeps its place in the markup. a div the author wrote is already in the right place and is deliberately left where it is: moving it would push the field past anything the enhancer does not touch, such as a <dd> holding only a button
|
|
57
|
+
dt.before(div);div.append(dt,dd);
|
|
32
58
|
// determine visibility of newly created <div>
|
|
33
|
-
if(dt.style.display==="none"&&dd.style.display==="none")div.style.display="none"}
|
|
59
|
+
if(dt.style.display==="none"&&dd.style.display==="none")div.style.display="none"}}else{newLabel.setAttribute("for",input.id);newLabel.innerHTML=label.innerHTML;if(input.hasAttribute("title")&&label.getAttribute("data-show-help-icon")!==null&&!label.querySelector("span.help")){const text=input.getAttribute("title");newLabel.innerHTML+=` <span title="${text}" class="help">${icons.helpIcon}</span>`}if(input.hasAttribute("required")&&label.getAttribute("data-no-asterisk")===null&&!label.querySelector("span.required")){const text=label.getAttribute("data-asterisk-text")||"This field is required.";newLabel.innerHTML+=` <span title="${text}" class="required">*</span>`}label.setAttribute("hidden","hidden");insertAfter(newLabel,input)}
|
|
34
60
|
// #endregion
|
|
35
61
|
// #region standard inputs
|
|
36
62
|
// check for auto-grow attribute on textareas
|
|
@@ -39,27 +65,30 @@ if(input.getAttribute("data-auto-grow")!==null){
|
|
|
39
65
|
if(input.nodeName==="INPUT"&&input.type==="text"){const newInput=document.createElement("textarea");newInput.id=input.id;newInput.class=input.class;newInput.innerText=input.value;newInput.setAttribute("data-auto-grow","");input.replaceWith(newInput);input=newInput}if(input.nodeName==="TEXTAREA")
|
|
40
66
|
// when pressing enter while this input is focused, we want to submit
|
|
41
67
|
input.addEventListener("keypress",e=>{if(e.key!=="Enter"||e.key==="Enter"&&e.shiftKey)return;e.preventDefault();form.requestSubmit()})}if(type!=="checkbox"&&type!=="radio"){if(!input.getAttribute("placeholder"))input.setAttribute("placeholder"," ");const div=isWrapped?input.closest("dd").parentElement:document.createElement("div");const dt=label.closest("dt");const dd=input.closest("dd");if(!dt||!dd){console.error(`semantic-forms: Found an input (${input.id||input.getAttribute("name")}) that does not have a corresponding ${!dt?"<dt>":"<dd>"} element.`);return}
|
|
42
|
-
// input clear button
|
|
43
|
-
if(input.nodeName!=="SELECT"&&type!=="range"){const clearBtn=document.createElement("button");clearBtn.type="button";clearBtn.title=input.getAttribute("data-clear-field-text")||"Clear field";clearBtn.ariaLabel=input.getAttribute("data-clear-field-text")||"Clear field";clearBtn.tabIndex=-1;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" />';clearBtn.classList.add("clear");clearBtn.id=`semanticFormsClearButton_${input.id}`;clearBtn.addEventListener("click",()=>{input.previousValue=input.value;input.value="";input.focus();lastClearFieldPressed=input.id;
|
|
68
|
+
// input clear button. a field the user cannot edit does not get one, since the button would empty it anyway. a field that only becomes uneditable later is handled in css
|
|
69
|
+
if(input.nodeName!=="SELECT"&&type!=="range"&&!input.readOnly&&!input.disabled){const clearBtn=document.createElement("button");clearBtn.type="button";clearBtn.title=input.getAttribute("data-clear-field-text")||"Clear field";clearBtn.ariaLabel=input.getAttribute("data-clear-field-text")||"Clear field";clearBtn.tabIndex=-1;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" />';clearBtn.classList.add("clear");clearBtn.id=`semanticFormsClearButton_${input.id}`;clearBtn.addEventListener("click",()=>{input.previousValue=input.value;input.value="";input.focus();lastClearFieldPressed=input.id;
|
|
44
70
|
// used for any other updates required by various inputs
|
|
45
71
|
input.dispatchEvent(new Event("input",{bubbles:true}))});insertAfter(clearBtn,dd.querySelector("label"))}input.addEventListener("focus",event=>{if(event.target.nodeName==="INPUT")lastFocusedInput=event.target});
|
|
46
|
-
//
|
|
47
|
-
|
|
72
|
+
// the colspan- and col- utility classes are documented as going on the dd, but they size and place the grid item, which is the div wrapping it, so move any of them across
|
|
73
|
+
for(const utility of dd.className.match(/colspan-(?:[0-9]|full)|col-[0-9]/g)||[]){dd.classList.remove(utility);div.classList.add(utility)}
|
|
48
74
|
// check for max-content attribute
|
|
49
75
|
// this may be removed once fully supported in Firefox and Safari: https://caniuse.com/wf-field-sizing
|
|
50
76
|
if(input.getAttribute("data-max-content")!==null)if(!("fieldSizing"in document.createElement("input").style)){const adjustWidth=()=>{const value=input.value!==""?input.value:input.placeholder;const width=value.length*8+40;input.style.width=width+"px";input.style.maxWidth="100%"};adjustWidth();input.addEventListener("input",adjustWidth)}
|
|
51
77
|
// if the user did not wrap the input in a div, do it for them
|
|
52
|
-
if(!isWrapped){
|
|
78
|
+
if(!isWrapped){
|
|
79
|
+
// as above, the wrapper takes the <dt>'s position rather than being appended to the end
|
|
80
|
+
dt.before(div);div.append(dt,dd);
|
|
53
81
|
// determine visibility of newly created <div>
|
|
54
|
-
if(dt.style.display==="none"&&dd.style.display==="none")div.style.display="none"}
|
|
82
|
+
if(dt.style.display==="none"&&dd.style.display==="none")div.style.display="none"}}
|
|
55
83
|
// #endregion
|
|
56
84
|
// handle file input clear btn, cannot be handled with CSS
|
|
57
85
|
if(type==="file"){const clearBtn=input.parentElement.querySelector(".clear");input.addEventListener("input",event=>{clearBtn.style.display=event.target.files.length?"flex":"none"});clearBtn.addEventListener("click",()=>{clearBtn.style.display="none"})}
|
|
58
|
-
// handle range inputs
|
|
59
|
-
|
|
60
|
-
if(type==="range"&&input.classList.contains("displayValue")){const label=input.parentNode.parentNode.querySelector("dd label");label.innerHTML+=`<span class="seperator">: </span><output>${input.value}</output>`;input.addEventListener("input",event=>{const output=event.target.parentNode.parentNode.querySelector("output");output.innerHTML=event.target.value})}
|
|
86
|
+
// handle range inputs set to display the value. every other per-field flag in the library is a data attribute, so that is the documented way to ask for this, but the original `displayValue` class is still honored
|
|
87
|
+
if(type==="range"&&(input.getAttribute("data-display-value")!==null||input.classList.contains("displayValue"))){const label=input.parentNode.parentNode.querySelector("dd label");label.innerHTML+=`<span class="seperator">: </span><output>${input.value}</output>`;input.addEventListener("input",event=>{const output=event.target.parentNode.parentNode.querySelector("output");output.innerHTML=event.target.value})}
|
|
61
88
|
// show password button
|
|
62
89
|
if(type==="password"&&input.getAttribute("data-no-reveal")===null){const showBtn=document.createElement("button");showBtn.type="button";showBtn.title=input.getAttribute("data-show-password-text")||"Show password";showBtn.ariaLabel=input.getAttribute("data-show-password-text")||"Show password";showBtn.tabIndex=-1;showBtn.innerHTML=icons.passwordShow;showBtn.classList.add("show");showBtn.id=`semanticFormsShowButton_${input.id}`;const dd=input.closest("dd");if(!dd){console.error(`semantic-forms: Found an input (${input.id||input.getAttribute("name")}) that is not inside a <dd> element.`);return}showBtn.addEventListener("click",()=>{if(input.type==="password"){showBtn.innerHTML=icons.passwordHide;showBtn.title=input.getAttribute("data-hide-password-text")||"Hide password";showBtn.ariaLabel=input.getAttribute("data-hide-password-text")||"Hide password";input.type="text"}else{showBtn.innerHTML=icons.passwordShow;showBtn.title=input.getAttribute("data-show-password-text")||"Show password";showBtn.ariaLabel=input.getAttribute("data-show-password-text")||"Show password";input.type="password"}input.focus()});insertAfter(showBtn,dd.querySelector("label"))}
|
|
90
|
+
// a checkbox with the switch attribute is a switch; the stylesheet draws one in every browser, and where the attribute is not implemented natively the role and dragging are supplied here
|
|
91
|
+
if(type==="checkbox"&&input.hasAttribute("switch")&&!nativeSwitchSupport){input.setAttribute("role","switch");enableSwitchDragging(input)}
|
|
63
92
|
// add listener to shift clear button when scrollbar present
|
|
64
93
|
if(input.nodeName==="TEXTAREA"){if(input.getAttribute("data-disable-autosize")===null){
|
|
65
94
|
// add auto-sizing
|
|
@@ -92,15 +121,15 @@ const shiftClearBtn=()=>{const clearBtn=input.parentElement?.querySelector("butt
|
|
|
92
121
|
// handle undo/redo
|
|
93
122
|
if((event.ctrlKey||event.metaKey)&&event.key==="z"&&!event.shiftKey){
|
|
94
123
|
// undo clearing a field
|
|
95
|
-
if(lastFocusedInput)if(lastFocusedInput?.parentNode?.querySelector("button.clear")
|
|
124
|
+
if(lastFocusedInput)if(lastFocusedInput?.parentNode?.querySelector("button.clear")?.id===`semanticFormsClearButton_${lastClearFieldPressed}`||lastFocusedInput?.parentNode?.querySelector("button.clear")?.name===`semanticFormsClearButton_${lastClearFieldPressed}`)if(lastFocusedInput.previousValue){
|
|
125
|
+
// the browser undoes the field's own edit history on this key too, which would apply on top of the value restored here, so the native behavior is suppressed
|
|
126
|
+
event.preventDefault();lastFocusedInput.redoValue=lastFocusedInput.value;lastFocusedInput.value=lastFocusedInput.previousValue}}else if(event.ctrlKey&&event.key==="y"||event.metaKey&&event.shiftKey&&event.key==="z")
|
|
96
127
|
// redo clearing a field
|
|
97
|
-
if(lastFocusedInput)if(lastFocusedInput?.parentNode?.querySelector("button.clear")
|
|
128
|
+
if(lastFocusedInput)if(lastFocusedInput?.parentNode?.querySelector("button.clear")?.id===`semanticFormsClearButton_${lastClearFieldPressed}`||lastFocusedInput?.parentNode?.querySelector("button.clear")?.name===`semanticFormsClearButton_${lastClearFieldPressed}`)if(lastFocusedInput.redoValue){
|
|
129
|
+
// as above, the browser's own redo would otherwise apply on top of this
|
|
130
|
+
event.preventDefault();lastFocusedInput.previousValue=lastFocusedInput.value;lastFocusedInput.value=lastFocusedInput.redoValue}};module.exports={enhanceInput,handleUndoRedo}
|
|
98
131
|
/***/},
|
|
99
|
-
/***/"./lib/keyboardShortcuts.js":
|
|
100
|
-
/*!**********************************!*\
|
|
101
|
-
!*** ./lib/keyboardShortcuts.js ***!
|
|
102
|
-
\**********************************/
|
|
103
|
-
/***/(module,__unused_webpack_exports,__webpack_require__)=>{const{getOS,insertAfter}=__webpack_require__(/*! ./helpers.js */"./lib/helpers.js");const specialCharMap={Minus:"-",Equal:"=",BracketLeft:"[",BracketRight:"]",Backslash:"\\",Semicolon:";",Quote:"'",Comma:",",Period:".",Slash:"/",Backquote:"`"};const shiftSpecialCharMap={1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")",Minus:"_",Equal:"+",BracketLeft:"{",BracketRight:"}",Backslash:"|",Semicolon:":",Quote:'"',Comma:"<",Period:">",Slash:"?",Backquote:"~"};
|
|
132
|
+
/***/"./lib/keyboardShortcuts.js"(module,__unused_webpack_exports,__webpack_require__){const{getOS,insertAfter}=__webpack_require__(/*! ./helpers.js */"./lib/helpers.js");const specialCharMap={Minus:"-",Equal:"=",BracketLeft:"[",BracketRight:"]",Backslash:"\\",Semicolon:";",Quote:"'",Comma:",",Period:".",Slash:"/",Backquote:"`"};const shiftSpecialCharMap={1:"!",2:"@",3:"#",4:"$",5:"%",6:"^",7:"&",8:"*",9:"(",0:")",Minus:"_",Equal:"+",BracketLeft:"{",BracketRight:"}",Backslash:"|",Semicolon:":",Quote:'"',Comma:"<",Period:">",Slash:"?",Backquote:"~"};
|
|
104
133
|
// handles keyboard shortcut events
|
|
105
134
|
const shortcutListener=(e,shortcuts)=>{
|
|
106
135
|
// search for matching shortcut from cached shortcut configs
|
|
@@ -130,46 +159,84 @@ const indicator=document.createElement("span");indicator.classList.add("focus-ke
|
|
|
130
159
|
// update the input title
|
|
131
160
|
if(input.getAttribute("title"))input.setAttribute("title",input.getAttribute("title")+` (${modifierSymbol} + ${focusKey})`);else input.setAttribute("title",`Focus with ${modifierSymbol} + ${focusKey}`);return{key:focusKey,modifier:modifierKey,input,os,defaultModifier}};module.exports={shortcutListener,createKeyboardShortcut}
|
|
132
161
|
/***/},
|
|
133
|
-
/***/"./
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
/***/"./lib/tabs.js"(module){let tabGroupCount=0;
|
|
163
|
+
// turns a group of fieldsets into a set of tabs
|
|
164
|
+
|
|
165
|
+
// the markup it enhances is an element with the `tabs` class holding one `<fieldset>` per tab, each titled by its `<legend>`. left alone that is a stack of labelled field groups, which is a perfectly usable form, so a browser without JavaScript still gets everything
|
|
166
|
+
|
|
167
|
+
// form: form element to search for tab groups
|
|
168
|
+
const enhanceTabs=form=>{for(const group of form.querySelectorAll(".tabs:not(.semanticFormsTabsActive)")){const panels=Array.from(group.children).filter(child=>child.nodeName==="FIELDSET");
|
|
169
|
+
// one fieldset is not a set of tabs, and no fieldsets means the class is on the wrong element
|
|
170
|
+
if(panels.length<2){if(panels.length===0)console.error("semantic-forms: Found a .tabs element with no <fieldset> children. Each tab needs to be a <fieldset> with a <legend> naming it.",group);continue}group.classList.add("semanticFormsTabsActive");const groupId=++tabGroupCount;const tabList=document.createElement("div");const tabs=[];tabList.classList.add("tabList");tabList.setAttribute("role","tablist");
|
|
171
|
+
// shows one panel and marks its tab as the selected one
|
|
172
|
+
const selectTab=(index,moveFocus)=>{for(const[i,panel]of panels.entries())panel.hidden=i!==index;for(const[i,tab]of tabs.entries()){const selected=i===index;tab.setAttribute("aria-selected",selected?"true":"false");
|
|
173
|
+
// only the selected tab is in the tab order; the arrow keys reach the others
|
|
174
|
+
tab.tabIndex=selected?0:-1}if(moveFocus)tabs[index].focus()};for(const[index,panel]of panels.entries()){const legend=panel.querySelector("legend");const tab=document.createElement("button");tab.type="button";tab.setAttribute("role","tab");tab.id=`semanticFormsTab_${groupId}_${index}`;tab.textContent=legend?legend.textContent:`Tab ${index+1}`;if(!panel.id)panel.id=`semanticFormsTabPanel_${groupId}_${index}`;panel.setAttribute("role","tabpanel");panel.setAttribute("aria-labelledby",tab.id);tab.setAttribute("aria-controls",panel.id);
|
|
175
|
+
// the tab now names the panel, so the legend would be read out twice
|
|
176
|
+
if(legend)legend.hidden=true;tab.addEventListener("click",()=>selectTab(index));tabs.push(tab);tabList.append(tab)}
|
|
177
|
+
// the arrow keys move between tabs, which is what a tablist is expected to do
|
|
178
|
+
tabList.addEventListener("keydown",event=>{const current=tabs.indexOf(document.activeElement);if(current===-1)return;const keys={ArrowRight:(current+1)%tabs.length,ArrowLeft:(current-1+tabs.length)%tabs.length,Home:0,End:tabs.length-1};if(!(event.key in keys))return;event.preventDefault();selectTab(keys[event.key],true)});group.prepend(tabList);selectTab(0)}};module.exports={enhanceTabs}
|
|
179
|
+
/***/},
|
|
180
|
+
/***/"./semanticForms.js"(module,__unused_webpack_exports,__webpack_require__){const{createKeyboardShortcut,shortcutListener}=__webpack_require__(/*! ./lib/keyboardShortcuts.js */"./lib/keyboardShortcuts.js");const{enhanceInput,handleUndoRedo}=__webpack_require__(/*! ./lib/inputEnhancements.js */"./lib/inputEnhancements.js");const{enhanceTabs}=__webpack_require__(/*! ./lib/tabs.js */"./lib/tabs.js");const{setInkOffset}=__webpack_require__(/*! ./lib/helpers.js */"./lib/helpers.js");
|
|
181
|
+
// one observer per tree that has been handed to semanticForms, so that a shadow root gets its own without replacing the document's
|
|
182
|
+
const observers=new WeakMap;
|
|
183
|
+
// progressively enhances every semanticForms form in a tree
|
|
184
|
+
|
|
185
|
+
// root: document or shadow root to search; defaults to the main document
|
|
186
|
+
const semanticForms=(root=document)=>{
|
|
138
187
|
// do some feature detection so none of the JS executes if the browser is too old
|
|
139
188
|
if(typeof document.getElementsByClassName!=="function"||typeof document.querySelector!=="function"||!document.body.classList||!window.MutationObserver){console.warn("semantic-forms was loaded into an unsupported browser and will not execute.");return}
|
|
140
189
|
// custom keyboard shortcut listener
|
|
141
190
|
const keyboardShortcuts=[];document.addEventListener("keydown",e=>shortcutListener(e,keyboardShortcuts));
|
|
142
191
|
// progressively enhance form elements that have the semanticForms class
|
|
143
|
-
const forms=
|
|
192
|
+
const forms=root.querySelectorAll("form.semanticForms:not(.semanticFormsActive), table.semanticForms:not(.semanticFormsActive)");for(const form of forms){form.classList.add("semanticFormsActive");if(form.classList.contains("lowFlow"))continue;
|
|
193
|
+
// 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
|
|
194
|
+
setInkOffset(form,form);
|
|
144
195
|
// update each input in the semantic form
|
|
145
196
|
const inputs=Array.from(form.querySelectorAll("input, textarea, select"));for(const input of inputs){enhanceInput(input,form);
|
|
146
197
|
// handle keyboard shortcuts
|
|
147
|
-
if(input.getAttribute("data-focus-key")!==null){const shortcut=createKeyboardShortcut(input,keyboardShortcuts);keyboardShortcuts.push(shortcut)}}
|
|
198
|
+
if(input.getAttribute("data-focus-key")!==null){const shortcut=createKeyboardShortcut(input,keyboardShortcuts);keyboardShortcuts.push(shortcut)}}
|
|
199
|
+
// done after the inputs so that anything sizing itself measures while it is still on screen
|
|
200
|
+
enhanceTabs(form)}
|
|
148
201
|
// prevents multiple listeners
|
|
149
202
|
document.removeEventListener("keydown",handleUndoRedo);document.addEventListener("keydown",handleUndoRedo);
|
|
150
|
-
// monitor changes to
|
|
151
|
-
if(!
|
|
203
|
+
// monitor changes to this tree and enhance new semanticForms forms that get added to it
|
|
204
|
+
if(!observers.has(root)){const observer=new window.MutationObserver(mutations=>{let stop=false;for(const mutation of mutations){for(const node of mutation.addedNodes)if(node.nodeName==="FORM"||node?.querySelector?.("form")){semanticForms(root);stop=true}if(stop)break}});
|
|
205
|
+
// a shadow root is observed directly; the document is observed through its body
|
|
206
|
+
observer.observe(root===document?document.body:root,{attributes:false,childList:true,characterData:false,subtree:true});observers.set(root,observer);
|
|
207
|
+
// long standing name for the document's observer, kept for anything already using it
|
|
208
|
+
if(root===document)window.semanticFormsObserver=observer}semanticForms.reinitialize=form=>{
|
|
209
|
+
// with no form given this just rescans for forms that are not active yet
|
|
210
|
+
if(!form)return semanticForms(root);form.classList.remove("semanticFormsActive");
|
|
211
|
+
// the form knows which tree it belongs to, so a form in a shadow root reinitializes there
|
|
212
|
+
semanticForms(form.getRootNode())}};module.exports=semanticForms
|
|
152
213
|
/***/}
|
|
153
214
|
/******/};
|
|
154
215
|
/************************************************************************/
|
|
155
216
|
/******/ // The module cache
|
|
156
|
-
/******/
|
|
217
|
+
/******/const __webpack_module_cache__={};
|
|
157
218
|
/******/
|
|
158
219
|
/******/ // The require function
|
|
159
220
|
/******/function __webpack_require__(moduleId){
|
|
160
221
|
/******/ // Check if module is in cache
|
|
161
|
-
/******/
|
|
222
|
+
/******/const cachedModule=__webpack_module_cache__[moduleId];
|
|
162
223
|
/******/if(cachedModule!==void 0)
|
|
163
224
|
/******/return cachedModule.exports;
|
|
164
225
|
/******/
|
|
165
226
|
/******/ // Create a new module (and put it into the cache)
|
|
166
|
-
/******/
|
|
227
|
+
/******/const module=__webpack_module_cache__[moduleId]={
|
|
167
228
|
/******/ // no module.id needed
|
|
168
229
|
/******/ // no module.loaded needed
|
|
169
230
|
/******/exports:{}
|
|
170
231
|
/******/};
|
|
171
232
|
/******/
|
|
172
233
|
/******/ // Execute the module function
|
|
234
|
+
/******/if(!(moduleId in __webpack_modules__)){
|
|
235
|
+
/******/delete __webpack_module_cache__[moduleId];
|
|
236
|
+
/******/const e=new Error("Cannot find module '"+moduleId+"'");
|
|
237
|
+
/******/e.code="MODULE_NOT_FOUND";
|
|
238
|
+
/******/throw e;
|
|
239
|
+
/******/}
|
|
173
240
|
/******/__webpack_modules__[moduleId](module,module.exports,__webpack_require__);
|
|
174
241
|
/******/
|
|
175
242
|
/******/ // Return the exports of the module
|
|
@@ -181,7 +248,7 @@ if(!window.semanticFormsObserver){window.semanticFormsObserver=new window.Mutati
|
|
|
181
248
|
/******/ // startup
|
|
182
249
|
/******/ // Load entry module and return exports
|
|
183
250
|
/******/ // This entry module is referenced by other modules so it can't be inlined
|
|
184
|
-
/******/
|
|
251
|
+
/******/let __webpack_exports__=__webpack_require__("./semanticForms.js");
|
|
185
252
|
/******/
|
|
186
253
|
/******/return __webpack_exports__;
|
|
187
254
|
/******/})());
|