@johnhenry/definable 0.0.0
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/LICENSE +21 -0
- package/README.md +73 -0
- package/package.json +35 -0
- package/src/define-component-by-content.component/global.mjs +3 -0
- package/src/define-component-by-content.component/index.mjs +28 -0
- package/src/define-component-by-content.component/readme.md +32 -0
- package/src/define-component.component/demo-widget.mjs +5 -0
- package/src/define-component.component/demo.html +3 -0
- package/src/define-component.component/global.mjs +2 -0
- package/src/define-component.component/index.mjs +36 -0
- package/src/define-component.component/readme.md +32 -0
- package/src/definetag/index.mjs +13 -0
- package/src/definetag/readme.md +18 -0
- package/src/polyfill-window.component/broadcast-channel-polyfill.mjs +89 -0
- package/src/polyfill-window.component/demo.html +21 -0
- package/src/polyfill-window.component/global.mjs +2 -0
- package/src/polyfill-window.component/index.mjs +35 -0
- package/src/polyfill-window.component/readme.md +30 -0
- package/src/polyfill-window.component/shout.mjs +1 -0
- package/src/until-window-load/global.mjs +2 -0
- package/src/until-window-load/index.mjs +9 -0
- package/src/until-window-load/readme.md +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Henry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# definable
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@johnhenry/definable)
|
|
4
|
+
[](https://github.com/johnhenry/definable/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Full documentation: [opensource.johnhenry.me/definable](https://opensource.johnhenry.me/definable/)
|
|
8
|
+
|
|
9
|
+
> **Provenance:** extracted from [`johnhenry/lib`](https://github.com/johnhenry/lib)'s
|
|
10
|
+
> `js/` directory. Briefly part of [`@johnhenry/domkit`](https://github.com/johnhenry/domkit)
|
|
11
|
+
> (a toolkit of ~40 independent DOM/HTML-component modules), then split out
|
|
12
|
+
> as its own package because this cluster has a real internal shape of its
|
|
13
|
+
> own -- four ways of getting behavior onto a page declaratively, from four
|
|
14
|
+
> different kinds of source.
|
|
15
|
+
|
|
16
|
+
Five small modules for declaratively loading or defining custom elements
|
|
17
|
+
and globals, without writing a `customElements.define(...)` call by hand:
|
|
18
|
+
|
|
19
|
+
| Module | What it does |
|
|
20
|
+
|---|---|
|
|
21
|
+
| [`definetag`](src/definetag/readme.md) | Curried `customElements.define` wrapper: `(elementClass) => (name) => ...`. The low-level primitive the others build on. |
|
|
22
|
+
| [`define-component.component`](src/define-component.component/readme.md) | `<define-component name="x" src="./x.mjs">` -- dynamically imports a module by URL and registers its export as a custom element. |
|
|
23
|
+
| [`define-component-by-content.component`](src/define-component-by-content.component/readme.md) | `<define-component-by-content name="x" content="<p>Hi</p>">` -- defines a custom element from an inline HTML string instead of a separate module file. Built on [`@johnhenry/domable`](https://github.com/johnhenry/domable)'s `simple-element`. |
|
|
24
|
+
| [`polyfill-window.component`](src/polyfill-window.component/readme.md) | `<polyfill-window name="x" src="./x.mjs">` -- the same URL-loading idea as `define-component`, but assigns the import to a `globalThis` key instead of registering a custom element. |
|
|
25
|
+
| [`until-window-load`](src/until-window-load/readme.md) | Declarative "remove this loading class once `window` has loaded" helper -- used by `polyfill-window.component`'s own demo to avoid a flash of unstyled content. |
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm install @johnhenry/definable
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import definetag from "@johnhenry/definable/definetag/index.mjs";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
or, in a browser with no build step, via a CDN:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<script
|
|
41
|
+
type="module"
|
|
42
|
+
src="https://esm.sh/@johnhenry/definable/define-component.component/global.mjs"
|
|
43
|
+
></script>
|
|
44
|
+
<define-component name="my-widget" src="./my-widget.mjs"></define-component>
|
|
45
|
+
<my-widget></my-widget>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Which one do I want?
|
|
49
|
+
|
|
50
|
+
| I have... | Use |
|
|
51
|
+
|---|---|
|
|
52
|
+
| A module file (default or named export is a custom-element class) | `define-component.component` |
|
|
53
|
+
| A small HTML string I want to become a tag | `define-component-by-content.component` |
|
|
54
|
+
| A module whose export should just be a global, not a tag | `polyfill-window.component` |
|
|
55
|
+
| My own class already, just need to register it under a name | `definetag` directly |
|
|
56
|
+
|
|
57
|
+
## Family
|
|
58
|
+
|
|
59
|
+
- [`@johnhenry/domable`](https://github.com/johnhenry/domable) -- `define-component-by-content.component`
|
|
60
|
+
depends on its `simple-element` `constructSuperclass` for building the
|
|
61
|
+
actual custom-element class from an HTML string.
|
|
62
|
+
- [`@johnhenry/domkit`](https://github.com/johnhenry/domkit) -- the toolkit
|
|
63
|
+
this package was originally part of. Several of its still-resident
|
|
64
|
+
modules (`animate-paths.component`, `chernoff-face`, `xy-grapher`) depend
|
|
65
|
+
on `definable` (`definetag`, `define-component.component`) to register
|
|
66
|
+
themselves.
|
|
67
|
+
- [`@johnhenry/respondable`](https://github.com/johnhenry/respondable) --
|
|
68
|
+
`query-container.component`'s own demo bootstraps via this package's
|
|
69
|
+
`define-component.component`.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@johnhenry/definable",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Load or define custom elements declaratively -- from a URL, from inline HTML content, or as a plain global polyfill.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./*": "./src/*"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src/"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node scripts/check-syntax.mjs"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"html",
|
|
17
|
+
"dom",
|
|
18
|
+
"web-components",
|
|
19
|
+
"custom-elements",
|
|
20
|
+
"define"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/johnhenry/definable.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://opensource.johnhenry.me/definable/",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=26.0.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@johnhenry/domable": "^0.0.1"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { constructSuperclass } from "@johnhenry/domable/simple-element";
|
|
2
|
+
|
|
3
|
+
export default class extends globalThis.HTMLElement {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
}
|
|
7
|
+
connectedCallback() {
|
|
8
|
+
const name = this.getAttribute("name");
|
|
9
|
+
const useShadow = this.getAttribute("shadow") || false;
|
|
10
|
+
const shadowMode = this.getAttribute("mode") || "open";
|
|
11
|
+
const content = this.getAttribute("content") || "";
|
|
12
|
+
const useDom = this.getAttribute("use-dom") || "";
|
|
13
|
+
// this.querySelector("slot").innerHTML = content;
|
|
14
|
+
|
|
15
|
+
globalThis.customElements.define(
|
|
16
|
+
name,
|
|
17
|
+
class extends constructSuperclass({
|
|
18
|
+
[useDom ? "HTML" : "shadowHTML"]: content,
|
|
19
|
+
useShadow,
|
|
20
|
+
shadowMode,
|
|
21
|
+
}) {
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Define Component By Content
|
|
2
|
+
|
|
3
|
+
Defines a new custom element whose markup comes from an HTML string given
|
|
4
|
+
directly as an attribute, rather than from a separate module file (compare
|
|
5
|
+
[define-component.component](../define-component.component/readme.md),
|
|
6
|
+
which loads markup/behavior from a URL instead).
|
|
7
|
+
|
|
8
|
+
Built on [`@johnhenry/domable`](https://github.com/johnhenry/domable)'s
|
|
9
|
+
`simple-element` `constructSuperclass`.
|
|
10
|
+
|
|
11
|
+
## Attributes
|
|
12
|
+
|
|
13
|
+
| Attribute | Description |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `name` | Tag name to register (required) |
|
|
16
|
+
| `content` | HTML string to render |
|
|
17
|
+
| `use-dom` | If present, `content` renders as light DOM (`append`ed directly); if absent (default), `content` renders inside an open shadow root |
|
|
18
|
+
| `mode` | Shadow root mode when `use-dom` is absent. Defaults to `"open"` |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<script
|
|
24
|
+
type="module"
|
|
25
|
+
src="https://esm.sh/@johnhenry/definable/define-component-by-content.component/global.mjs"
|
|
26
|
+
></script>
|
|
27
|
+
<define-component-by-content
|
|
28
|
+
name="my-greeting"
|
|
29
|
+
content="<p>Hello!</p>"
|
|
30
|
+
></define-component-by-content>
|
|
31
|
+
<my-greeting></my-greeting>
|
|
32
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Dynamically imports a module and registers its default (or named) export
|
|
2
|
+
// as a custom element. Adapted from polyfill-window.component/index.mjs's
|
|
3
|
+
// URL-resolution logic, but ends in customElements.define(...) instead of a
|
|
4
|
+
// global assignment -- see readme.md.
|
|
5
|
+
const define = async (src, name, imp, force) => {
|
|
6
|
+
const { href } = globalThis.location;
|
|
7
|
+
const indexQM = href.lastIndexOf("?");
|
|
8
|
+
const withoutQuery = indexQM === -1 ? href : href.substring(0, indexQM);
|
|
9
|
+
const indexS = withoutQuery.lastIndexOf("/");
|
|
10
|
+
const dirname =
|
|
11
|
+
indexS === -1 ? withoutQuery : withoutQuery.substring(0, indexS);
|
|
12
|
+
const url = new URL(src, dirname + "/");
|
|
13
|
+
if (globalThis.customElements.get(name) && force === null) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const module = await import(url.href);
|
|
17
|
+
const ElementClass = module[imp ?? "default"];
|
|
18
|
+
globalThis.customElements.define(name, ElementClass);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default class extends globalThis.HTMLElement {
|
|
22
|
+
#name = "";
|
|
23
|
+
#src = "";
|
|
24
|
+
#import = null;
|
|
25
|
+
#force = false;
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
}
|
|
29
|
+
connectedCallback() {
|
|
30
|
+
this.#name = this.getAttribute("name");
|
|
31
|
+
this.#src = this.getAttribute("src");
|
|
32
|
+
this.#import = this.getAttribute("import");
|
|
33
|
+
this.#force = this.getAttribute("force");
|
|
34
|
+
define(this.#src, this.#name, this.#import, this.#force);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Define Component
|
|
2
|
+
|
|
3
|
+
Dynamically loads a module by URL and registers its export as a custom
|
|
4
|
+
element — the generic "define a component from a URL" pattern referenced
|
|
5
|
+
by [polyfill-window.component](../polyfill-window.component/readme.md)'s
|
|
6
|
+
own demo, and by [`@johnhenry/respondable`](https://github.com/johnhenry/respondable)'s
|
|
7
|
+
`query-container.component` and [`@johnhenry/domkit`](https://github.com/johnhenry/domkit)'s
|
|
8
|
+
`animate-paths.component`, both of which bootstrap via this exact pattern.
|
|
9
|
+
|
|
10
|
+
Compare [define-component-by-content.component](../define-component-by-content.component/readme.md),
|
|
11
|
+
which defines a component from an inline HTML string instead of a separate
|
|
12
|
+
module file.
|
|
13
|
+
|
|
14
|
+
## Attributes
|
|
15
|
+
|
|
16
|
+
| Attribute | Description |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `name` | Tag name to register (required) |
|
|
19
|
+
| `src` | URL of the module to import, resolved relative to the current document (required) |
|
|
20
|
+
| `import` | Named export to use as the element class. Defaults to the module's default export |
|
|
21
|
+
| `force` | If present, re-imports and re-registers even if `name` is already a registered custom element |
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<script
|
|
27
|
+
type="module"
|
|
28
|
+
src="https://esm.sh/@johnhenry/definable/define-component.component/global.mjs"
|
|
29
|
+
></script>
|
|
30
|
+
<define-component name="my-widget" src="./my-widget.mjs"></define-component>
|
|
31
|
+
<my-widget></my-widget>
|
|
32
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default (elementClass) => (name) =>
|
|
2
|
+
globalThis.customElements.define(name, elementClass);
|
|
3
|
+
|
|
4
|
+
// export default (elementClass) => (name) => {
|
|
5
|
+
// try {
|
|
6
|
+
// globalThis.customElements.define(name, elementClass);
|
|
7
|
+
// } catch (e) {
|
|
8
|
+
// console.error(e);
|
|
9
|
+
// } finally {
|
|
10
|
+
// console.log({ elementClass });
|
|
11
|
+
// console.log({ name });
|
|
12
|
+
// }
|
|
13
|
+
// };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Define Tag
|
|
2
|
+
|
|
3
|
+
A curried `customElements.define` wrapper: `(elementClass) => (name) =>
|
|
4
|
+
customElements.define(name, elementClass)`. Useful for separating "what a
|
|
5
|
+
component's class is" from "what tag name it gets registered under" —
|
|
6
|
+
several `.component` modules' `define.mjs`/`global.mjs` files use this
|
|
7
|
+
instead of calling `customElements.define` directly. See
|
|
8
|
+
[define-component-by-content.component](../define-component-by-content.component/readme.md)
|
|
9
|
+
for a real consumer.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import definetag from "../definetag/index.mjs";
|
|
15
|
+
import MyComponent from "./my-component.mjs";
|
|
16
|
+
|
|
17
|
+
definetag(MyComponent)("my-component");
|
|
18
|
+
```
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// https://gist.github.com/sechel/e6aff22d9e56df02c5bd09c4afc516e6
|
|
2
|
+
(function (global) {
|
|
3
|
+
var channels = [];
|
|
4
|
+
|
|
5
|
+
function BroadcastFlannel(channel) {
|
|
6
|
+
var $this = this;
|
|
7
|
+
channel = String(channel);
|
|
8
|
+
|
|
9
|
+
var id = "$BroadcastFlannel$" + channel + "$";
|
|
10
|
+
|
|
11
|
+
channels[id] = channels[id] || [];
|
|
12
|
+
channels[id].push(this);
|
|
13
|
+
|
|
14
|
+
this._name = channel;
|
|
15
|
+
this._id = id;
|
|
16
|
+
this._closed = false;
|
|
17
|
+
this._mc = new MessageChannel();
|
|
18
|
+
this._mc.port1.start();
|
|
19
|
+
this._mc.port2.start();
|
|
20
|
+
|
|
21
|
+
global.addEventListener("storage", function (e) {
|
|
22
|
+
if (e.storageArea !== global.localStorage) return;
|
|
23
|
+
if (e.newValue === null) return;
|
|
24
|
+
if (e.key.substring(0, id.length) !== id) return;
|
|
25
|
+
var data = JSON.parse(e.newValue);
|
|
26
|
+
$this._mc.port2.postMessage(data);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
BroadcastFlannel.prototype = {
|
|
31
|
+
// BroadcastFlannel API
|
|
32
|
+
get name() {
|
|
33
|
+
return this._name;
|
|
34
|
+
},
|
|
35
|
+
postMessage: function (message) {
|
|
36
|
+
var $this = this;
|
|
37
|
+
if (this._closed) {
|
|
38
|
+
var e = new Error();
|
|
39
|
+
e.name = "InvalidStateError";
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
var value = JSON.stringify(message);
|
|
43
|
+
|
|
44
|
+
// Broadcast to other contexts via storage events...
|
|
45
|
+
var key = this._id + String(Date.now()) + "$" + String(Math.random());
|
|
46
|
+
global.localStorage.setItem(key, value);
|
|
47
|
+
setTimeout(function () {
|
|
48
|
+
global.localStorage.removeItem(key);
|
|
49
|
+
}, 500);
|
|
50
|
+
|
|
51
|
+
// Broadcast to current context via ports
|
|
52
|
+
channels[this._id].forEach(function (bc) {
|
|
53
|
+
if (bc === $this) return;
|
|
54
|
+
bc._mc.port2.postMessage(JSON.parse(value));
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
close: function () {
|
|
58
|
+
if (this._closed) return;
|
|
59
|
+
this._closed = true;
|
|
60
|
+
this._mc.port1.close();
|
|
61
|
+
this._mc.port2.close();
|
|
62
|
+
|
|
63
|
+
var index = channels[this._id].indexOf(this);
|
|
64
|
+
channels[this._id].splice(index, 1);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// EventTarget API
|
|
68
|
+
get onmessage() {
|
|
69
|
+
return this._mc.port1.onmessage;
|
|
70
|
+
},
|
|
71
|
+
set onmessage(value) {
|
|
72
|
+
this._mc.port1.onmessage = value;
|
|
73
|
+
},
|
|
74
|
+
addEventListener: function (type, listener /*, useCapture*/) {
|
|
75
|
+
return this._mc.port1.addEventListener.apply(this._mc.port1, arguments);
|
|
76
|
+
},
|
|
77
|
+
removeEventListener: function (type, listener /*, useCapture*/) {
|
|
78
|
+
return this._mc.port1.removeEventListener.apply(
|
|
79
|
+
this._mc.port1,
|
|
80
|
+
arguments
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
dispatchEvent: function (event) {
|
|
84
|
+
return this._mc.port1.dispatchEvent.apply(this._mc.port1, arguments);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
global.BroadcastFlannel = global.BroadcastFlannel || BroadcastFlannel;
|
|
89
|
+
})(globalThis);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.until-window-load {
|
|
3
|
+
visibility: hidden;
|
|
4
|
+
}
|
|
5
|
+
</style>
|
|
6
|
+
<script type="module" src="../until-window-load/index.mjs"></script>
|
|
7
|
+
<script
|
|
8
|
+
type="module"
|
|
9
|
+
src="../define-component.component/global.mjs"
|
|
10
|
+
></script>
|
|
11
|
+
<define-component
|
|
12
|
+
class="until-window-load"
|
|
13
|
+
name="polyfill-window"
|
|
14
|
+
src="./index.mjs"
|
|
15
|
+
></define-component>
|
|
16
|
+
<polyfill-window name="shout" src="./shout-polyfill.mjs"></polyfill-window>
|
|
17
|
+
<polyfill-window
|
|
18
|
+
name="BroadcastFhannel"
|
|
19
|
+
src="./broadcast-channel-polyfill.mjs"
|
|
20
|
+
no-import
|
|
21
|
+
></polyfill-window>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const define = async (src, name, imp, force, noImport) => {
|
|
2
|
+
const { href } = globalThis.location;
|
|
3
|
+
const indexQM = href.lastIndexOf("?");
|
|
4
|
+
const withoutQuery = indexQM === -1 ? href : href.substring(0, indexQM);
|
|
5
|
+
const indexS = withoutQuery.lastIndexOf("/");
|
|
6
|
+
const dirname =
|
|
7
|
+
indexS === -1 ? withoutQuery : withoutQuery.substring(0, indexS);
|
|
8
|
+
const url = new URL(src, dirname + "/");
|
|
9
|
+
if (!globalThis[name] || force !== null) {
|
|
10
|
+
const module = await import(url.href);
|
|
11
|
+
if (!noImport) {
|
|
12
|
+
const E = module[imp ?? "default"];
|
|
13
|
+
globalThis[name] = E;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default class extends globalThis.HTMLElement {
|
|
19
|
+
#name = "";
|
|
20
|
+
#src = "";
|
|
21
|
+
#import = null;
|
|
22
|
+
#force = false;
|
|
23
|
+
#noImport = false;
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
}
|
|
27
|
+
connectedCallback() {
|
|
28
|
+
this.#name = this.getAttribute("name");
|
|
29
|
+
this.#src = this.getAttribute("src");
|
|
30
|
+
this.#import = this.getAttribute("import");
|
|
31
|
+
this.#force = this.getAttribute("force");
|
|
32
|
+
this.#noImport = this.getAttribute("no-import");
|
|
33
|
+
define(this.#src, this.#name, this.#import, this.#force, this.#noImport);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Polyfill Window
|
|
2
|
+
|
|
3
|
+
Dynamically imports a module and assigns its export to a key on
|
|
4
|
+
`globalThis` -- for loading a global polyfill declaratively from HTML,
|
|
5
|
+
rather than registering a custom element (compare
|
|
6
|
+
[define-component.component](../define-component.component/readme.md),
|
|
7
|
+
which this module's own `demo.html` uses to bootstrap itself and which
|
|
8
|
+
solves the related "register a custom element from a URL" problem).
|
|
9
|
+
|
|
10
|
+
## Attributes
|
|
11
|
+
|
|
12
|
+
| Attribute | Description |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `name` | Global key to assign the import to (required) |
|
|
15
|
+
| `src` | URL of the module to import, resolved relative to the current document (required) |
|
|
16
|
+
| `import` | Named export to assign. Defaults to the module's default export |
|
|
17
|
+
| `force` | If present, re-imports and re-assigns even if `globalThis[name]` is already set |
|
|
18
|
+
| `no-import` | If present, the module is imported (for its side effects) but nothing is assigned to `globalThis` |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<script
|
|
24
|
+
type="module"
|
|
25
|
+
src="https://esm.sh/@johnhenry/definable/define-component.component/global.mjs"
|
|
26
|
+
></script>
|
|
27
|
+
<define-component name="polyfill-window" src="./index.mjs"></define-component>
|
|
28
|
+
|
|
29
|
+
<polyfill-window name="shout" src="./shout-polyfill.mjs"></polyfill-window>
|
|
30
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default (text) => alert(text.toUpperCase());
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Until Window Load
|
|
2
|
+
|
|
3
|
+
Declarative import to remove loading classes once the window has loaded.
|
|
4
|
+
|
|
5
|
+
Useful to avoid content flash for custom components that arrange content
|
|
6
|
+
after loading. Used by [polyfill-window.component](../polyfill-window.component/readme.md)'s
|
|
7
|
+
own demo.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
### Manual
|
|
12
|
+
|
|
13
|
+
Define the class to be removed:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import removeLoadingClasses from "https://esm.sh/@johnhenry/definable/until-window-load/index.mjs";
|
|
17
|
+
removeLoadingClasses("custom-loading-class");
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
.custom-loading-class {
|
|
22
|
+
visibility: hidden;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<custom-component class="custom-loading-class"
|
|
28
|
+
>Hide me until window load</custom-component
|
|
29
|
+
>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Automatic
|
|
33
|
+
|
|
34
|
+
Use the "global" import to automatically use the class name `until-window-load`:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script
|
|
38
|
+
type="module"
|
|
39
|
+
src="https://esm.sh/@johnhenry/definable/until-window-load/global.mjs"
|
|
40
|
+
></script>
|
|
41
|
+
<style>
|
|
42
|
+
.until-window-load {
|
|
43
|
+
visibility: hidden;
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
46
|
+
<custom-component class="until-window-load"
|
|
47
|
+
>Hide me until window load</custom-component
|
|
48
|
+
>
|
|
49
|
+
```
|