Skip to content

Commit 039154c

Browse files
committed
Avoid ES modules for webc components
1 parent 28a2c2a commit 039154c

File tree

10 files changed

+391
-14
lines changed

10 files changed

+391
-14
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
# Changes
22

3-
## v0.1.1 - 2024-07-27
3+
## v0.1.3 - 2024-08-06
4+
5+
- FIX: Don't use ES modules for webC templates
6+
7+
## v0.1.2 - 2024-07-27
48

59
- FIX: `index.js` is the main package entry point
610
- Use default exports
711

8-
## v0.1.2 - 2024-07-26
12+
## v0.1.0 - 2024-07-26
913

1014
Initial Features…
1115

CONTRIBUTING.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Contributions are welcome!
2+
3+
Before making major changes,
4+
[open an issue in GitHub](https://github.com/mirisuzanne/ground-control/issues/)
5+
for discussion.
6+
7+
1. Clone
8+
[the source from Github](https://github.com/mirisuzanne/ground-control),
9+
and make changes directly
10+
to `ground-control.js`,
11+
`input-control.js`,
12+
and/or `toggle-control.js`.
13+
14+
2. Make sure any changes
15+
are reflected in working demos
16+
(`index.html`).
17+
18+
3. Copy any changes
19+
into the `*-raw.js` files --
20+
which should contain simple class definitions
21+
and registrations,
22+
without any module import/export syntax
23+
24+
4. Commit changes,
25+
and open a pull request.

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# `ground-control`
22

33
A Web Component for
4-
providing user interactions
5-
that control HTML attributes
4+
user control of HTML attributes
65
and CSS properties
76
on other elements of the page.
87

@@ -80,15 +79,15 @@ Make sure you include the `<script>` in your project
8079
<!-- 3rd party CDN, not recommended for production use -->
8180
<script
8281
type="module"
83-
src="https://www.unpkg.com/@terriblemia/[email protected].2/index.js"
82+
src="https://www.unpkg.com/@terriblemia/[email protected].3/index.js"
8483
></script>
8584
```
8685

8786
```html
8887
<!-- 3rd party CDN, not recommended for production use -->
8988
<script
9089
type="module"
91-
src="https://esm.sh/@terriblemia/[email protected].2"
90+
src="https://esm.sh/@terriblemia/[email protected].3"
9291
></script>
9392
```
9493

ground-control-raw.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
class GroundControl extends HTMLElement {
2+
static observedAttributes = [
3+
'data-for', // <selector-list> of elements to update…
4+
// by setting either…
5+
'data-attr', // <attribute-name>
6+
'data-prop', // <css-property-name>
7+
// and optionally storing the value for later…
8+
'data-local', // <localStorage key>
9+
'data-session', // <sessionStorage key>
10+
// override event listeners (mainly for inputs)
11+
'data-event',
12+
// provide an 'off' value (mainly for toggles)
13+
'data-off',
14+
];
15+
16+
static register(tagName) {
17+
if ('customElements' in window) {
18+
customElements.define(
19+
tagName || 'ground-control',
20+
GroundControl
21+
);
22+
}
23+
}
24+
25+
static _appendShadowTemplate = (node) => {
26+
const template = document.createElement('template');
27+
const shadowRoot = node.attachShadow({ mode: 'open' });
28+
template.innerHTML = `<slot></slot>`;
29+
shadowRoot.appendChild(template.content.cloneNode(true));
30+
}
31+
static _adoptShadowStyles = (node) => {
32+
const shadowStyle = new CSSStyleSheet();
33+
shadowStyle.replaceSync(`:host { display: block }`);
34+
node.shadowRoot.adoptedStyleSheets = [shadowStyle];
35+
}
36+
37+
#related = {};
38+
#inputId;
39+
#currentValue;
40+
hasInput;
41+
initialValue;
42+
43+
constructor() {
44+
super();
45+
GroundControl._appendShadowTemplate(this);
46+
GroundControl._adoptShadowStyles(this);
47+
}
48+
49+
attributeChangedCallback(name, oldValue, newValue) {
50+
if (newValue === oldValue) return;
51+
52+
switch (name) {
53+
case 'data-for':
54+
this.targets = newValue;
55+
break;
56+
case 'data-local':
57+
localStorage.removeItem(oldValue);
58+
break;
59+
case 'data-session':
60+
sessionStorage.removeItem(oldValue);
61+
break;
62+
case 'data-event':
63+
if (this.onEventChange) this.onEventChange();
64+
break;
65+
}
66+
this.broadCast();
67+
}
68+
69+
connectedCallback() {
70+
this.targets = this.dataset.for;
71+
}
72+
73+
disconnectedCallback() {
74+
this.#removeResetListener();
75+
}
76+
77+
// getters and setters
78+
set value(newValue) {
79+
this.#currentValue = newValue;
80+
if (this.onValueChange) this.onValueChange();
81+
this.broadCast();
82+
}
83+
84+
get value() { return this.#currentValue; }
85+
get usedValue() {
86+
return this.value || this.dataset.off || '';
87+
}
88+
89+
set inputId(value) {
90+
this.#inputId = value;
91+
92+
this.#removeResetListener();
93+
this.#related.resets = this.#findAll(
94+
`button[data-reset~=${value}]`
95+
);
96+
this.#addResetListener();
97+
98+
this.#related.displays = this.#findAll(
99+
`output[for=${value}]`
100+
);
101+
}
102+
103+
set targets(to) {
104+
this.#related.targets = (to && typeof to === 'object')
105+
? to
106+
: this.#findAll(to || ':root');
107+
}
108+
109+
get targets() { return this.#related.targets || []; }
110+
get displays() { return this.#related.displays || []; }
111+
112+
get storedValue() {
113+
return sessionStorage.getItem(this.dataset.session)
114+
|| localStorage.getItem(this.dataset.local);
115+
}
116+
117+
set storedValue(value) {
118+
const clearWhen = [
119+
this.initialValue,
120+
undefined, 'undefined',
121+
];
122+
123+
this.#updateStorage(clearWhen.includes(value));
124+
}
125+
126+
// public methods
127+
onValueChange;
128+
onEventChange;
129+
130+
onReset = () => {
131+
this.value = this.initialValue;
132+
};
133+
134+
broadCast = () => {
135+
if (!this.hasInput) { return; }
136+
137+
this.storedValue = this.value;
138+
139+
this.targets.forEach((el) => {
140+
if (this.dataset.prop) {
141+
el.style.setProperty(this.dataset.prop, this.usedValue);
142+
}
143+
if (this.dataset.attr) {
144+
el.setAttribute(this.dataset.attr, this.usedValue);
145+
}
146+
});
147+
148+
this.displays.forEach((el) => {
149+
el.value = this.usedValue;
150+
});
151+
};
152+
153+
#updateStorage = (clear) => {
154+
if (this.dataset.local) {
155+
clear
156+
? localStorage.removeItem(this.dataset.local)
157+
: localStorage.setItem(this.dataset.local, this.value);
158+
}
159+
160+
if (this.dataset.session) {
161+
clear
162+
? sessionStorage.removeItem(this.dataset.session)
163+
: sessionStorage.setItem(this.dataset.session, this.value);
164+
}
165+
}
166+
167+
// private methods
168+
#findAll = (selector) => [
169+
...document.querySelectorAll(selector)
170+
];
171+
172+
#addResetListener = () => {
173+
this.#related.resets.forEach((resetBtn) => {
174+
resetBtn.addEventListener('click', this.onReset);
175+
});
176+
}
177+
178+
#removeResetListener = () => {
179+
if (!this.#related.resets) { return; }
180+
181+
this.#related.resets.forEach((resetBtn) => {
182+
resetBtn.removeEventListener('click', this.onReset);
183+
});
184+
}
185+
}

input-control-raw.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
class InputControl extends GroundControl {
2+
static register(tagName) {
3+
if ('customElements' in window) {
4+
customElements.define(
5+
tagName || 'input-control',
6+
InputControl
7+
);
8+
}
9+
}
10+
11+
static rejectInputTypes = [
12+
'file',
13+
'submit', 'reset', 'button', 'image',
14+
'checkbox', 'radio'
15+
];
16+
17+
#input;
18+
19+
constructor() {
20+
super();
21+
22+
this.addEventListener('change', this.onInputChange);
23+
this.addEventListener('input', this.onInputChange);
24+
}
25+
26+
connectedCallback() {
27+
super.connectedCallback();
28+
this.#input = this.#findInput();
29+
30+
if (!this.#input) {
31+
console.error('No input found for input-control');
32+
return;
33+
}
34+
35+
this.hasInput = true;
36+
if (this.#input.id) this.inputId = this.#input.id;
37+
38+
this.initialValue = this.#input.value;
39+
this.value = this.storedValue || this.initialValue;
40+
}
41+
42+
onInputChange = (event) => {
43+
const onType = this.dataset.event || 'change';
44+
if (onType !== event.type) return;
45+
46+
this.value = this.#input.value;
47+
}
48+
49+
onValueChange = () => {
50+
this.#input.value = this.value;
51+
}
52+
53+
#findInput = () => {
54+
const inputSelect = InputControl.rejectInputTypes.reduce(
55+
(all, type) => {
56+
const item = `[type=${type}]`;
57+
return all ? `${all}, ${item}` : item;
58+
}
59+
);
60+
61+
return this.querySelector(`select, input:not(${inputSelect})`);
62+
}
63+
}
64+
65+
InputControl.register();

input-control.webc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<script src="./ground-control.js"></script>
2-
<script src="./input-control.js"></script>
1+
<script src="./ground-control-raw.js"></script>
2+
<script src="./input-control-raw.js"></script>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@terriblemia/ground-control",
3-
"version": "0.1.2",
4-
"description": "A Web Component for",
3+
"version": "0.1.3",
4+
"description": "A Web Component for user control of HTML attributes and CSS properties",
55
"main": "index.js",
66
"scripts": {
77
"dev": "npx http-server ."

0 commit comments

Comments
 (0)