u2ui 1.6.0 → 1.7.5
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/attr/disableif/README.md +21 -0
- package/attr/disableif/disableif.js +72 -45
- package/package.json +1 -1
package/attr/disableif/README.md
CHANGED
|
@@ -19,6 +19,26 @@ Disable form-elements (like inputs, buttons, fieldsets) based on a condition
|
|
|
19
19
|
}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
## Conditions
|
|
23
|
+
|
|
24
|
+
| condition | disabled while |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `name` | a value of `name` is not empty |
|
|
27
|
+
| `!name` | no value of `name` is not empty |
|
|
28
|
+
| `name=v` | a value equals `v` |
|
|
29
|
+
| `name!=v` | no value equals `v` |
|
|
30
|
+
| `name>n`, `name<n` | a value is a number greater / less than `n` |
|
|
31
|
+
|
|
32
|
+
A field's values are what the form would send under its name (`FormData`): none for an unchecked
|
|
33
|
+
checkbox or a disabled field, several for same-named fields or a `select multiple`, a file by its name.
|
|
34
|
+
So a field disabled by its own condition counts as empty for the conditions that depend on it, and
|
|
35
|
+
chains settle in one go. Each check builds the `FormData`, which fires the form's `formdata` event.
|
|
36
|
+
A field that is not in the form counts as empty, with a warning (a typo?).
|
|
37
|
+
|
|
38
|
+
Typing applies at once; fields and conditions added, removed or changed in the form apply on the
|
|
39
|
+
next microtask. A value set by script (`input.value = …`) is only seen with an `input` event.
|
|
40
|
+
Removing the attribute leaves the element as it is.
|
|
41
|
+
|
|
22
42
|
## Install
|
|
23
43
|
|
|
24
44
|
```html
|
|
@@ -29,6 +49,7 @@ Disable form-elements (like inputs, buttons, fieldsets) based on a condition
|
|
|
29
49
|
|
|
30
50
|
[test.html](http://gcdn.li/u2ui/u2@main/attr/disableif/tests/test.html)
|
|
31
51
|
[minimal.html](http://gcdn.li/u2ui/u2@main/attr/disableif/tests/minimal.html)
|
|
52
|
+
[disableif.test.html](http://gcdn.li/u2ui/u2@main/attr/disableif/tests/disableif.test.html) (tests)
|
|
32
53
|
|
|
33
54
|
## About
|
|
34
55
|
|
|
@@ -1,59 +1,86 @@
|
|
|
1
1
|
import { SelectorObserver } from '../../js/SelectorObserver/SelectorObserver.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (operator) console.warn(`u2-disableif: "!" operator is not supported with other operators`, { el, field, operator, value });
|
|
11
|
-
operator = '=';
|
|
12
|
-
}
|
|
13
|
-
const target = el.form.elements[field];
|
|
14
|
-
if (!target) {
|
|
15
|
-
console.warn(`u2-disableif: field "${field}" not found`, { el, field, operator, value });
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
let stringTargetValue = target.value;
|
|
19
|
-
if (target.type === 'checkbox') stringTargetValue = target.checked ? target.value : '';
|
|
3
|
+
/** Once per element and problem, not on every update. */
|
|
4
|
+
const warned = new WeakMap();
|
|
5
|
+
function warn(el, problem) {
|
|
6
|
+
if (warned.get(el) === problem) return;
|
|
7
|
+
warned.set(el, problem);
|
|
8
|
+
console.warn('u2-disableif: ' + problem, el);
|
|
9
|
+
}
|
|
20
10
|
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Whether the element's condition holds. A field's values are what the form would send under its
|
|
13
|
+
* name (FormData): none for an unchecked box or a disabled field, several for same-named fields or
|
|
14
|
+
* a select multiple; a file counts by its name. undefined: no valid condition (yet).
|
|
15
|
+
*/
|
|
16
|
+
function holds(el, data) {
|
|
17
|
+
const match = el.getAttribute('u2-disableif').match(/^(!?)([^=!<>]*)(=|!=|>|<)?([^=!<>]*)$/);
|
|
18
|
+
if (!match) return;
|
|
19
|
+
const [, not, field, operator, value] = match;
|
|
20
|
+
const form = el.form;
|
|
21
|
+
// a custom element that is not defined yet is no form field yet: check again once it is
|
|
22
|
+
const pending = !form.elements[field] && form.querySelector(`:not(:defined)[name="${CSS.escape(field)}"]`);
|
|
23
|
+
if (pending) { customElements.whenDefined(pending.localName).then(() => schedule(form)); return; }
|
|
24
|
+
if (not && operator) { warn(el, `"!" cannot be combined with "${operator}"`); return; }
|
|
25
|
+
if (!form.elements[field]) warn(el, `field "${field}" not found, so it is empty`); // a typo?
|
|
26
|
+
const values = data.getAll(field).map(v => typeof v === 'string' ? v : v.name);
|
|
27
|
+
switch (operator) {
|
|
28
|
+
case '=': return values.includes(value);
|
|
29
|
+
case '!=': return !values.includes(value);
|
|
30
|
+
case '>': return values.some(v => parseFloat(v) > parseFloat(value));
|
|
31
|
+
case '<': return values.some(v => parseFloat(v) < parseFloat(value));
|
|
32
|
+
}
|
|
33
|
+
return values.some(Boolean) !== !!not;
|
|
34
|
+
}
|
|
23
35
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
conditionMet = numericTargetValue > numericConditionValue;
|
|
37
|
-
break;
|
|
38
|
-
case '<':
|
|
39
|
-
conditionMet = numericTargetValue < numericConditionValue;
|
|
40
|
-
break;
|
|
36
|
+
/** Applies all conditions of the form, again until nothing changes: disabling a field empties it for
|
|
37
|
+
* the conditions that depend on it. A cycle stops after as many passes as there are conditions. */
|
|
38
|
+
function update(form) {
|
|
39
|
+
const els = [...form.elements].filter(el => el.hasAttribute('u2-disableif'));
|
|
40
|
+
for (let pass = 0; pass <= els.length; pass++) {
|
|
41
|
+
const data = new FormData(form); // fires "formdata", once per pass
|
|
42
|
+
let changed = false;
|
|
43
|
+
for (const el of els) {
|
|
44
|
+
const disabled = holds(el, data);
|
|
45
|
+
if (disabled === undefined || disabled === el.disabled) continue;
|
|
46
|
+
el.disabled = disabled;
|
|
47
|
+
changed = true;
|
|
41
48
|
}
|
|
42
|
-
|
|
49
|
+
if (!changed) return;
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
|
|
53
|
+
/** One update per form and `wait` (microtask, frame), however many changes ask for it. */
|
|
54
|
+
const scheduled = new Map();
|
|
55
|
+
function schedule(form, wait = queueMicrotask) {
|
|
56
|
+
const forms = scheduled.get(wait) ?? scheduled.set(wait, new WeakSet()).get(wait);
|
|
57
|
+
if (!form || forms.has(form)) return;
|
|
58
|
+
forms.add(form);
|
|
59
|
+
wait(() => { forms.delete(form); update(form); });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Typing applies at once. Fields and conditions that are added, removed or changed in the form
|
|
63
|
+
* apply on the next microtask; one that changes its form also updates the new one. */
|
|
64
|
+
const ATTRIBUTES = ['u2-disableif', 'name', 'form', 'value', 'checked', 'selected', 'multiple', 'type'];
|
|
65
|
+
const watched = new WeakSet();
|
|
66
|
+
function watch(form) {
|
|
67
|
+
if (!form || watched.has(form)) return;
|
|
68
|
+
watched.add(form);
|
|
69
|
+
form.addEventListener('input', () => update(form));
|
|
70
|
+
form.addEventListener('reset', () => setTimeout(() => update(form))); // the values change after the event
|
|
71
|
+
new MutationObserver(records => {
|
|
72
|
+
schedule(form);
|
|
73
|
+
for (const { target } of records) if (target.form !== form) { watch(target.form); schedule(target.form); }
|
|
74
|
+
}).observe(form, { subtree: true, childList: true, attributes: true, attributeFilter: ATTRIBUTES });
|
|
75
|
+
}
|
|
46
76
|
|
|
47
77
|
new SelectorObserver({
|
|
48
78
|
on: (el) => {
|
|
49
|
-
|
|
79
|
+
watch(el.form);
|
|
80
|
+
schedule(el.form);
|
|
50
81
|
// wait for custom-element-inputs
|
|
51
|
-
// queueMicrotask(()=>
|
|
52
|
-
|
|
53
|
-
//setTimeout(()=>
|
|
54
|
-
const form = el.form;
|
|
55
|
-
el.form.addEventListener('input', () => checkCondition(el));
|
|
82
|
+
// queueMicrotask(() => schedule(el.form));
|
|
83
|
+
schedule(el.form, requestAnimationFrame);
|
|
84
|
+
// setTimeout(() => schedule(el.form));
|
|
56
85
|
},
|
|
57
|
-
off: (el) => {
|
|
58
|
-
}
|
|
59
86
|
}).observe('[u2-disableif]');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u2ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.5",
|
|
4
4
|
"description": "HTML Advanced: More Capabilities, Same Feel, Same Simplicity",
|
|
5
5
|
"keywords": ["web-components","pattern-library","component-library","ui-toolkit","design-system"],
|
|
6
6
|
"author": "Tobias Buschor",
|