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 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">&times;</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
+ })();
@@ -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
- /** Reads the `label` HTML attribute (button text), with a default. */
13
- private get label();
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. */
@@ -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
- /** Reads the `label` HTML attribute (button text), with a default. */
72
- get label() {
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.label)}</button>
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.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": ["web-components", "custom-elements", "popup", "modal", "button"],
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
  }