popup-button-plugin 0.1.1 → 0.1.3
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/README.md +12 -0
- package/dist/popup-button-plugin.global.js +169 -0
- package/dist/popup-button.d.ts +12 -2
- package/dist/popup-button.js +13 -3
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -10,6 +10,18 @@ See the full design write-up and integration steps in
|
|
|
10
10
|
[popup-button-prd.md](./popup-button-prd.md) and
|
|
11
11
|
[popup-button-integration-guide.md](./popup-button-integration-guide.md).
|
|
12
12
|
|
|
13
|
+
## Supported languages / frameworks (tested)
|
|
14
|
+
|
|
15
|
+
| Language / Framework | Supported | Example |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Plain HTML | ✅ | `examples/html/` |
|
|
18
|
+
| React | ✅ | `examples/react-app/` |
|
|
19
|
+
| Vue | ✅ | `examples/vue-app/` |
|
|
20
|
+
| Svelte | ✅ | `examples/svelte-app/` |
|
|
21
|
+
| Angular | ✅ | `examples/angular-app/` |
|
|
22
|
+
| Flutter | ❌ not supported | — |
|
|
23
|
+
| React Native | ❌ not supported | — |
|
|
24
|
+
|
|
13
25
|
## Quick start
|
|
14
26
|
|
|
15
27
|
```bash
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var PopupButtonPlugin = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
PopupButton: () => PopupButton,
|
|
25
|
+
getButtonContent: () => getButtonContent,
|
|
26
|
+
registerButtonContent: () => registerButtonContent,
|
|
27
|
+
unregisterButtonContent: () => unregisterButtonContent
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/popup-registry.ts
|
|
31
|
+
var registry = /* @__PURE__ */ new Map();
|
|
32
|
+
function registerButtonContent(name, content) {
|
|
33
|
+
registry.set(name, content);
|
|
34
|
+
}
|
|
35
|
+
function getButtonContent(name) {
|
|
36
|
+
return registry.get(name);
|
|
37
|
+
}
|
|
38
|
+
function unregisterButtonContent(name) {
|
|
39
|
+
registry.delete(name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/popup-button.ts
|
|
43
|
+
var PopupButton = class extends HTMLElement {
|
|
44
|
+
constructor() {
|
|
45
|
+
super();
|
|
46
|
+
// Tracks whether the popup is currently visible.
|
|
47
|
+
this.isOpen = false;
|
|
48
|
+
/** Runs when the visible <button> inside our shadow DOM is clicked. */
|
|
49
|
+
this.handleButtonClick = () => {
|
|
50
|
+
const found = getButtonContent(this.buttonName);
|
|
51
|
+
if (!found) {
|
|
52
|
+
this.dispatchEvent(
|
|
53
|
+
new CustomEvent("popup-content-missing", {
|
|
54
|
+
detail: { name: this.buttonName },
|
|
55
|
+
bubbles: true
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.content = found;
|
|
61
|
+
this.isOpen = true;
|
|
62
|
+
this.render();
|
|
63
|
+
};
|
|
64
|
+
/** Runs when the popup's close button or backdrop is clicked. */
|
|
65
|
+
this.handleClose = () => {
|
|
66
|
+
this.isOpen = false;
|
|
67
|
+
this.render();
|
|
68
|
+
};
|
|
69
|
+
this.attachShadow({ mode: "open" });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Runs once, the first time this tag is attached to the page.
|
|
73
|
+
* (Equivalent to a component's "mount"/"componentDidMount".)
|
|
74
|
+
*/
|
|
75
|
+
connectedCallback() {
|
|
76
|
+
this.render();
|
|
77
|
+
}
|
|
78
|
+
/** Reads the `name` HTML attribute, e.g. <popup-button name="save-info">. */
|
|
79
|
+
get buttonName() {
|
|
80
|
+
return this.getAttribute("name") ?? "";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reads the `label` HTML attribute (button text), with a default.
|
|
84
|
+
*
|
|
85
|
+
* NOTE: named `buttonLabel`, NOT `label` — React 19 sets custom
|
|
86
|
+
* element props as real DOM properties (`el.label = '...'`) when
|
|
87
|
+
* the property exists on the element. A getter-only `label` here
|
|
88
|
+
* (no setter) would make that assignment throw
|
|
89
|
+
* "Cannot set property label ... which has only a getter" and
|
|
90
|
+
* crash the whole React render. Avoid naming internal accessors
|
|
91
|
+
* the same as attributes a framework might try to set directly.
|
|
92
|
+
*/
|
|
93
|
+
get buttonLabel() {
|
|
94
|
+
return this.getAttribute("label") ?? "Click me";
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Rebuilds this element's shadow DOM to match current state
|
|
98
|
+
* (isOpen / content). There's no automatic re-render like Stencil —
|
|
99
|
+
* we call this ourselves every time state changes.
|
|
100
|
+
*/
|
|
101
|
+
render() {
|
|
102
|
+
const shadow = this.shadowRoot;
|
|
103
|
+
shadow.innerHTML = `
|
|
104
|
+
<style>${STYLES}</style>
|
|
105
|
+
<button class="btn">${escapeHtml(this.buttonLabel)}</button>
|
|
106
|
+
${this.isOpen ? this.renderPopupMarkup() : ""}
|
|
107
|
+
`;
|
|
108
|
+
shadow.querySelector(".btn").addEventListener("click", this.handleButtonClick);
|
|
109
|
+
if (this.isOpen) {
|
|
110
|
+
shadow.querySelector(".backdrop").addEventListener("click", this.handleClose);
|
|
111
|
+
shadow.querySelector(".close").addEventListener("click", this.handleClose);
|
|
112
|
+
shadow.querySelector(".popup").addEventListener("click", (e) => e.stopPropagation());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
renderPopupMarkup() {
|
|
116
|
+
const title = this.content?.title ?? "";
|
|
117
|
+
const body = this.content?.body ?? "";
|
|
118
|
+
return `
|
|
119
|
+
<div class="backdrop">
|
|
120
|
+
<div class="popup">
|
|
121
|
+
<div class="popup__header">
|
|
122
|
+
<span class="popup__title">${escapeHtml(title)}</span>
|
|
123
|
+
<button class="close" aria-label="Close">×</button>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="popup__body">${escapeHtml(body)}</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function escapeHtml(value) {
|
|
132
|
+
const div = document.createElement("div");
|
|
133
|
+
div.textContent = value;
|
|
134
|
+
return div.innerHTML;
|
|
135
|
+
}
|
|
136
|
+
var STYLES = `
|
|
137
|
+
:host { display: inline-block; font-family: inherit; }
|
|
138
|
+
.btn {
|
|
139
|
+
padding: 8px 16px;
|
|
140
|
+
border-radius: 6px;
|
|
141
|
+
border: none;
|
|
142
|
+
background: #2563eb;
|
|
143
|
+
color: #fff;
|
|
144
|
+
font-size: 14px;
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
}
|
|
147
|
+
.btn:hover { background: #1d4ed8; }
|
|
148
|
+
.backdrop {
|
|
149
|
+
position: fixed; inset: 0;
|
|
150
|
+
background: rgba(0,0,0,0.5);
|
|
151
|
+
display: flex; align-items: center; justify-content: center;
|
|
152
|
+
z-index: 1000;
|
|
153
|
+
}
|
|
154
|
+
.popup {
|
|
155
|
+
background: #fff; border-radius: 10px;
|
|
156
|
+
min-width: 320px; max-width: 90vw;
|
|
157
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
158
|
+
}
|
|
159
|
+
.popup__header {
|
|
160
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
161
|
+
padding: 16px; border-bottom: 1px solid #e5e7eb;
|
|
162
|
+
}
|
|
163
|
+
.popup__title { font-size: 16px; font-weight: 700; color: #111827; }
|
|
164
|
+
.close { background: none; border: none; font-size: 20px; cursor: pointer; color: #6b7280; }
|
|
165
|
+
.popup__body { padding: 16px; color: #374151; }
|
|
166
|
+
`;
|
|
167
|
+
customElements.define("popup-button", PopupButton);
|
|
168
|
+
return __toCommonJS(index_exports);
|
|
169
|
+
})();
|
package/dist/popup-button.d.ts
CHANGED
|
@@ -9,8 +9,18 @@ export declare class PopupButton extends HTMLElement {
|
|
|
9
9
|
connectedCallback(): void;
|
|
10
10
|
/** Reads the `name` HTML attribute, e.g. <popup-button name="save-info">. */
|
|
11
11
|
private get buttonName();
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Reads the `label` HTML attribute (button text), with a default.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: named `buttonLabel`, NOT `label` — React 19 sets custom
|
|
16
|
+
* element props as real DOM properties (`el.label = '...'`) when
|
|
17
|
+
* the property exists on the element. A getter-only `label` here
|
|
18
|
+
* (no setter) would make that assignment throw
|
|
19
|
+
* "Cannot set property label ... which has only a getter" and
|
|
20
|
+
* crash the whole React render. Avoid naming internal accessors
|
|
21
|
+
* the same as attributes a framework might try to set directly.
|
|
22
|
+
*/
|
|
23
|
+
private get buttonLabel();
|
|
14
24
|
/** Runs when the visible <button> inside our shadow DOM is clicked. */
|
|
15
25
|
private handleButtonClick;
|
|
16
26
|
/** Runs when the popup's close button or backdrop is clicked. */
|
package/dist/popup-button.js
CHANGED
|
@@ -68,8 +68,18 @@ export class PopupButton extends HTMLElement {
|
|
|
68
68
|
var _a;
|
|
69
69
|
return (_a = this.getAttribute('name')) !== null && _a !== void 0 ? _a : '';
|
|
70
70
|
}
|
|
71
|
-
/**
|
|
72
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Reads the `label` HTML attribute (button text), with a default.
|
|
73
|
+
*
|
|
74
|
+
* NOTE: named `buttonLabel`, NOT `label` — React 19 sets custom
|
|
75
|
+
* element props as real DOM properties (`el.label = '...'`) when
|
|
76
|
+
* the property exists on the element. A getter-only `label` here
|
|
77
|
+
* (no setter) would make that assignment throw
|
|
78
|
+
* "Cannot set property label ... which has only a getter" and
|
|
79
|
+
* crash the whole React render. Avoid naming internal accessors
|
|
80
|
+
* the same as attributes a framework might try to set directly.
|
|
81
|
+
*/
|
|
82
|
+
get buttonLabel() {
|
|
73
83
|
var _a;
|
|
74
84
|
return (_a = this.getAttribute('label')) !== null && _a !== void 0 ? _a : 'Click me';
|
|
75
85
|
}
|
|
@@ -82,7 +92,7 @@ export class PopupButton extends HTMLElement {
|
|
|
82
92
|
const shadow = this.shadowRoot;
|
|
83
93
|
shadow.innerHTML = `
|
|
84
94
|
<style>${STYLES}</style>
|
|
85
|
-
<button class="btn">${escapeHtml(this.
|
|
95
|
+
<button class="btn">${escapeHtml(this.buttonLabel)}</button>
|
|
86
96
|
${this.isOpen ? this.renderPopupMarkup() : ''}
|
|
87
97
|
`;
|
|
88
98
|
// Re-attach listeners every render, since innerHTML = '...' above
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "popup-button-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A tiny, standalone, framework-free button-with-popup web component: register content by name, and <popup-button name=\"...\"> automatically shows it when clicked.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -9,13 +9,24 @@
|
|
|
9
9
|
"dist/"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"build": "tsc -p tsconfig.json && npm run build:global",
|
|
13
|
+
"build:global": "esbuild src/index.ts --bundle --format=iife --global-name=PopupButtonPlugin --outfile=dist/popup-button-plugin.global.js",
|
|
13
14
|
"start": "tsc -p tsconfig.json --watch",
|
|
14
15
|
"prepublishOnly": "npm run build"
|
|
15
16
|
},
|
|
16
|
-
"keywords": [
|
|
17
|
+
"keywords": [
|
|
18
|
+
"web-components",
|
|
19
|
+
"custom-elements",
|
|
20
|
+
"popup",
|
|
21
|
+
"modal",
|
|
22
|
+
"button"
|
|
23
|
+
],
|
|
17
24
|
"license": "MIT",
|
|
18
25
|
"devDependencies": {
|
|
26
|
+
"esbuild": "^0.28.2",
|
|
19
27
|
"typescript": "^5.6.0"
|
|
28
|
+
},
|
|
29
|
+
"allowScripts": {
|
|
30
|
+
"esbuild@0.28.2": true
|
|
20
31
|
}
|
|
21
32
|
}
|