@tillsc/progressive-web-components 0.1.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.
@@ -0,0 +1,259 @@
1
+ // src/core/pwc-element.js
2
+ var PwcElement = class extends HTMLElement {
3
+ /**
4
+ * List of DOM event types to bind on the host element.
5
+ * Subclasses may override.
6
+ *
7
+ * Example:
8
+ * static events = ["click", "input"];
9
+ */
10
+ static events = [];
11
+ static registerCss(cssText) {
12
+ const sheet = new CSSStyleSheet();
13
+ sheet.replaceSync(cssText);
14
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
15
+ }
16
+ connectedCallback() {
17
+ if (this._connected) return;
18
+ this._connected = true;
19
+ this._bindEvents();
20
+ }
21
+ disconnectedCallback() {
22
+ if (!this._connected) return;
23
+ this._connected = false;
24
+ this._unbindEvents();
25
+ this.onDisconnect();
26
+ }
27
+ /**
28
+ * Optional cleanup hook for subclasses.
29
+ */
30
+ onDisconnect() {
31
+ }
32
+ /**
33
+ * Bind declared events using the handleEvent pattern.
34
+ */
35
+ _bindEvents() {
36
+ const events = this.constructor.events ?? [];
37
+ for (const type of events) {
38
+ this.addEventListener(type, this);
39
+ }
40
+ }
41
+ /**
42
+ * Unbind all previously declared events.
43
+ */
44
+ _unbindEvents() {
45
+ const events = this.constructor.events ?? [];
46
+ for (const type of events) {
47
+ this.removeEventListener(type, this);
48
+ }
49
+ }
50
+ /**
51
+ * Default event handler.
52
+ * Subclasses are expected to override this method
53
+ * and route events as needed.
54
+ */
55
+ handleEvent(_event) {
56
+ }
57
+ };
58
+
59
+ // src/core/pwc-simple-init-element.js
60
+ var PwcSimpleInitElement = class extends PwcElement {
61
+ connectedCallback() {
62
+ if (this._connected) return;
63
+ super.connectedCallback();
64
+ queueMicrotask(() => {
65
+ if (!this._connected) return;
66
+ this.onConnect();
67
+ });
68
+ }
69
+ /**
70
+ * Hook for subclasses.
71
+ * Called once per connection, after microtask deferral.
72
+ */
73
+ onConnect() {
74
+ }
75
+ };
76
+
77
+ // src/modal-dialog/base.js
78
+ var ModalDialogBase = class extends PwcSimpleInitElement {
79
+ static events = ["click"];
80
+ onDisconnect() {
81
+ this._teardown();
82
+ }
83
+ get ui() {
84
+ if (!this._ui) throw new Error("ui is only available after open()");
85
+ return this._ui;
86
+ }
87
+ get rootEl() {
88
+ return this.ui.rootEl;
89
+ }
90
+ get bodyEl() {
91
+ return this.ui.bodyEl;
92
+ }
93
+ get headerEl() {
94
+ return this.ui.headerEl;
95
+ }
96
+ get footerEl() {
97
+ return this.ui.footerEl;
98
+ }
99
+ isOpen() {
100
+ return false;
101
+ }
102
+ open({ title = "", size = "lg", closeText = "Close", ...options }) {
103
+ if (!this.isConnected) {
104
+ this._autoRemove = true;
105
+ document.body.appendChild(this);
106
+ }
107
+ this._teardown();
108
+ const ui = this._render({ title, size, closeText, ...options });
109
+ this._ui = ui;
110
+ const parent = this._getOpenSibling();
111
+ this._parent = parent && parent !== ui.rootEl ? parent : null;
112
+ this._closed = false;
113
+ this._armFinalClose(ui, () => this._onFinalClose());
114
+ if (this._parent) {
115
+ this._parent.dataset.closeReason = "suspend";
116
+ this._suspend(this._parent);
117
+ }
118
+ this._show(ui, { title, size, closeText, ...options });
119
+ }
120
+ close() {
121
+ if (this._closed) return;
122
+ this._closed = true;
123
+ this.dataset.closeReason = "final";
124
+ this._hide(this._ui);
125
+ }
126
+ _onFinalClose() {
127
+ this._closed = true;
128
+ delete this.dataset.closeReason;
129
+ const parent = this._parent;
130
+ this._parent = null;
131
+ this._teardown();
132
+ if (parent && parent.isConnected) {
133
+ delete parent.dataset.closeReason;
134
+ queueMicrotask(() => this._restore(parent));
135
+ }
136
+ if (this._autoRemove && this.isConnected) this.remove();
137
+ }
138
+ handleEvent(e) {
139
+ if (e.type !== "click") return;
140
+ if (e.defaultPrevented) return;
141
+ const ui = this._ui;
142
+ if (!ui?.rootEl) return;
143
+ if (e.target === ui.rootEl) {
144
+ this.close();
145
+ return;
146
+ }
147
+ const closeEl = e.target.closest('[data-pwc-action="close"]');
148
+ if (!closeEl || !this.contains(closeEl)) return;
149
+ this.close();
150
+ }
151
+ _teardown() {
152
+ const ui = this._ui;
153
+ this._ui = null;
154
+ ui?.teardown?.();
155
+ }
156
+ };
157
+
158
+ // src/core/utils.js
159
+ function defineOnce(name, classDef) {
160
+ if (customElements.get(name)) return;
161
+ customElements.define(name, classDef);
162
+ }
163
+
164
+ // src/modal-dialog/bs5/modal-dialog.js
165
+ var PwcModalDialogBs5 = class extends ModalDialogBase {
166
+ static events = ["click", "hidden.bs.modal"];
167
+ onConnect() {
168
+ this.classList.add("modal", "fade");
169
+ this.tabIndex = -1;
170
+ this.setAttribute("aria-hidden", "true");
171
+ }
172
+ isOpen() {
173
+ return this.classList.contains("show");
174
+ }
175
+ requireBsModal() {
176
+ const BsModal = globalThis.bootstrap?.Modal;
177
+ if (!BsModal) throw new Error("Bootstrap Modal required (globalThis.bootstrap.Modal)");
178
+ return BsModal;
179
+ }
180
+ _render({ title, size, closeText, showClose = true }) {
181
+ this.innerHTML = `
182
+ <div class="modal-dialog modal-dialog-centered modal-${size}">
183
+ <div class="modal-content">
184
+ <div class="modal-header">
185
+ <h3 class="modal-title"></h3>
186
+ </div>
187
+ <div class="modal-body"></div>
188
+ <div class="modal-footer"></div>
189
+ </div>
190
+ </div>
191
+ `;
192
+ this.querySelector(".modal-title").textContent = title;
193
+ if (showClose) {
194
+ const btn = document.createElement("button");
195
+ btn.type = "button";
196
+ btn.className = "btn-close";
197
+ btn.setAttribute("aria-label", closeText);
198
+ btn.setAttribute("data-pwc-action", "close");
199
+ this.querySelector(".modal-header").appendChild(btn);
200
+ }
201
+ return {
202
+ rootEl: this,
203
+ bodyEl: this.querySelector(".modal-body"),
204
+ headerEl: this.querySelector(".modal-header"),
205
+ footerEl: this.querySelector(".modal-footer"),
206
+ modal: null,
207
+ teardown: () => {
208
+ const BsModal = this.requireBsModal();
209
+ BsModal.getInstance(this)?.dispose();
210
+ this.innerHTML = "";
211
+ this._finalClose = null;
212
+ }
213
+ };
214
+ }
215
+ _getOpenSibling() {
216
+ const el = document.querySelector(".modal.show");
217
+ if (el === this) return null;
218
+ return el;
219
+ }
220
+ _suspend(el) {
221
+ const BsModal = this.requireBsModal();
222
+ BsModal.getOrCreateInstance(el).hide();
223
+ }
224
+ _restore(el) {
225
+ const BsModal = this.requireBsModal();
226
+ BsModal.getOrCreateInstance(el).show();
227
+ }
228
+ _show(ui, { backdrop = true, keyboard = true, focus = true } = {}) {
229
+ const BsModal = this.requireBsModal();
230
+ ui.modal = BsModal.getOrCreateInstance(this, { backdrop, keyboard, focus });
231
+ ui.modal.show();
232
+ }
233
+ _hide(ui) {
234
+ ui.modal?.hide();
235
+ }
236
+ _armFinalClose(_ui, onFinalClose) {
237
+ this._finalClose = onFinalClose;
238
+ }
239
+ handleEvent(e) {
240
+ if (e.type === "hidden.bs.modal") {
241
+ if (this.dataset.closeReason === "suspend") return;
242
+ const fn = this._finalClose;
243
+ this._finalClose = null;
244
+ if (typeof fn === "function") fn();
245
+ return;
246
+ }
247
+ super.handleEvent(e);
248
+ }
249
+ };
250
+ var define = () => defineOnce("pwc-modal-dialog-bs5", PwcModalDialogBs5);
251
+
252
+ // src/modal-dialog/bs5/index.js
253
+ function register() {
254
+ define();
255
+ }
256
+ register();
257
+ export {
258
+ register
259
+ };
@@ -0,0 +1,251 @@
1
+ // src/core/pwc-element.js
2
+ var PwcElement = class extends HTMLElement {
3
+ /**
4
+ * List of DOM event types to bind on the host element.
5
+ * Subclasses may override.
6
+ *
7
+ * Example:
8
+ * static events = ["click", "input"];
9
+ */
10
+ static events = [];
11
+ static registerCss(cssText) {
12
+ const sheet = new CSSStyleSheet();
13
+ sheet.replaceSync(cssText);
14
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
15
+ }
16
+ connectedCallback() {
17
+ if (this._connected) return;
18
+ this._connected = true;
19
+ this._bindEvents();
20
+ }
21
+ disconnectedCallback() {
22
+ if (!this._connected) return;
23
+ this._connected = false;
24
+ this._unbindEvents();
25
+ this.onDisconnect();
26
+ }
27
+ /**
28
+ * Optional cleanup hook for subclasses.
29
+ */
30
+ onDisconnect() {
31
+ }
32
+ /**
33
+ * Bind declared events using the handleEvent pattern.
34
+ */
35
+ _bindEvents() {
36
+ const events = this.constructor.events ?? [];
37
+ for (const type of events) {
38
+ this.addEventListener(type, this);
39
+ }
40
+ }
41
+ /**
42
+ * Unbind all previously declared events.
43
+ */
44
+ _unbindEvents() {
45
+ const events = this.constructor.events ?? [];
46
+ for (const type of events) {
47
+ this.removeEventListener(type, this);
48
+ }
49
+ }
50
+ /**
51
+ * Default event handler.
52
+ * Subclasses are expected to override this method
53
+ * and route events as needed.
54
+ */
55
+ handleEvent(_event) {
56
+ }
57
+ };
58
+
59
+ // src/core/pwc-simple-init-element.js
60
+ var PwcSimpleInitElement = class extends PwcElement {
61
+ connectedCallback() {
62
+ if (this._connected) return;
63
+ super.connectedCallback();
64
+ queueMicrotask(() => {
65
+ if (!this._connected) return;
66
+ this.onConnect();
67
+ });
68
+ }
69
+ /**
70
+ * Hook for subclasses.
71
+ * Called once per connection, after microtask deferral.
72
+ */
73
+ onConnect() {
74
+ }
75
+ };
76
+
77
+ // src/modal-dialog/base.js
78
+ var ModalDialogBase = class extends PwcSimpleInitElement {
79
+ static events = ["click"];
80
+ onDisconnect() {
81
+ this._teardown();
82
+ }
83
+ get ui() {
84
+ if (!this._ui) throw new Error("ui is only available after open()");
85
+ return this._ui;
86
+ }
87
+ get rootEl() {
88
+ return this.ui.rootEl;
89
+ }
90
+ get bodyEl() {
91
+ return this.ui.bodyEl;
92
+ }
93
+ get headerEl() {
94
+ return this.ui.headerEl;
95
+ }
96
+ get footerEl() {
97
+ return this.ui.footerEl;
98
+ }
99
+ isOpen() {
100
+ return false;
101
+ }
102
+ open({ title = "", size = "lg", closeText = "Close", ...options }) {
103
+ if (!this.isConnected) {
104
+ this._autoRemove = true;
105
+ document.body.appendChild(this);
106
+ }
107
+ this._teardown();
108
+ const ui = this._render({ title, size, closeText, ...options });
109
+ this._ui = ui;
110
+ const parent = this._getOpenSibling();
111
+ this._parent = parent && parent !== ui.rootEl ? parent : null;
112
+ this._closed = false;
113
+ this._armFinalClose(ui, () => this._onFinalClose());
114
+ if (this._parent) {
115
+ this._parent.dataset.closeReason = "suspend";
116
+ this._suspend(this._parent);
117
+ }
118
+ this._show(ui, { title, size, closeText, ...options });
119
+ }
120
+ close() {
121
+ if (this._closed) return;
122
+ this._closed = true;
123
+ this.dataset.closeReason = "final";
124
+ this._hide(this._ui);
125
+ }
126
+ _onFinalClose() {
127
+ this._closed = true;
128
+ delete this.dataset.closeReason;
129
+ const parent = this._parent;
130
+ this._parent = null;
131
+ this._teardown();
132
+ if (parent && parent.isConnected) {
133
+ delete parent.dataset.closeReason;
134
+ queueMicrotask(() => this._restore(parent));
135
+ }
136
+ if (this._autoRemove && this.isConnected) this.remove();
137
+ }
138
+ handleEvent(e) {
139
+ if (e.type !== "click") return;
140
+ if (e.defaultPrevented) return;
141
+ const ui = this._ui;
142
+ if (!ui?.rootEl) return;
143
+ if (e.target === ui.rootEl) {
144
+ this.close();
145
+ return;
146
+ }
147
+ const closeEl = e.target.closest('[data-pwc-action="close"]');
148
+ if (!closeEl || !this.contains(closeEl)) return;
149
+ this.close();
150
+ }
151
+ _teardown() {
152
+ const ui = this._ui;
153
+ this._ui = null;
154
+ ui?.teardown?.();
155
+ }
156
+ };
157
+
158
+ // src/core/utils.js
159
+ function defineOnce(name, classDef) {
160
+ if (customElements.get(name)) return;
161
+ customElements.define(name, classDef);
162
+ }
163
+
164
+ // src/modal-dialog/modal-dialog.js
165
+ var PwcModalDialog = class extends ModalDialogBase {
166
+ isOpen() {
167
+ return Boolean(this._ui?.rootEl?.open);
168
+ }
169
+ _render({ title, size, closeText, showClose = true }) {
170
+ const dlg = document.createElement("dialog");
171
+ dlg.className = `pwc-modal-dialog pwc-modal-dialog--${size}`;
172
+ dlg.innerHTML = `
173
+ <div class="pwc-modal-dialog-surface" role="document">
174
+ <header class="pwc-modal-dialog-header">
175
+ <h3 class="pwc-modal-dialog-title"></h3>
176
+ </header>
177
+ <section class="pwc-modal-dialog-body"></section>
178
+ <footer class="pwc-modal-dialog-footer"></footer>
179
+ </div>
180
+ `;
181
+ dlg.querySelector(".pwc-modal-dialog-title").textContent = title;
182
+ if (showClose) {
183
+ const btn = document.createElement("button");
184
+ btn.type = "button";
185
+ btn.className = "pwc-modal-dialog-x";
186
+ btn.setAttribute("aria-label", closeText);
187
+ btn.setAttribute("data-pwc-action", "close");
188
+ btn.textContent = "\xD7";
189
+ dlg.querySelector(".pwc-modal-dialog-header").appendChild(btn);
190
+ }
191
+ this.replaceChildren(dlg);
192
+ return {
193
+ rootEl: dlg,
194
+ bodyEl: dlg.querySelector(".pwc-modal-dialog-body"),
195
+ headerEl: dlg.querySelector(".pwc-modal-dialog-header"),
196
+ footerEl: dlg.querySelector(".pwc-modal-dialog-footer"),
197
+ teardown: () => {
198
+ if (dlg.open) dlg.close();
199
+ dlg.remove();
200
+ }
201
+ };
202
+ }
203
+ _getOpenSibling() {
204
+ const candidates = Array.from(document.querySelectorAll("pwc-modal-dialog"));
205
+ return candidates.find((el) => el !== this && el._ui?.rootEl?.open === true) || null;
206
+ }
207
+ _suspend(hostEl) {
208
+ if (hostEl.isOpen()) hostEl.rootEl.close();
209
+ }
210
+ _restore(hostEl) {
211
+ const dlg = hostEl.rootEl;
212
+ if (dlg && typeof dlg.showModal === "function" && !dlg.open) dlg.showModal();
213
+ }
214
+ _show(ui) {
215
+ const dlg = ui.rootEl;
216
+ if (typeof dlg?.showModal !== "function") throw new Error("<dialog> not supported");
217
+ if (!dlg.open) dlg.showModal();
218
+ }
219
+ _hide(ui) {
220
+ const dlg = ui?.rootEl;
221
+ if (dlg?.open) dlg.close();
222
+ }
223
+ _armFinalClose(ui, onFinalClose) {
224
+ const dlg = ui?.rootEl;
225
+ if (!dlg) return;
226
+ const onClose = () => {
227
+ if (this.dataset.closeReason === "suspend") return;
228
+ onFinalClose();
229
+ };
230
+ dlg.addEventListener("close", onClose);
231
+ const prevTeardown = ui.teardown;
232
+ ui.teardown = () => {
233
+ dlg.removeEventListener("close", onClose);
234
+ if (prevTeardown) prevTeardown();
235
+ };
236
+ }
237
+ };
238
+ var define = () => defineOnce("pwc-modal-dialog", PwcModalDialog);
239
+
240
+ // src/modal-dialog/modal-dialog.css
241
+ var modal_dialog_default = "pwc-modal-dialog {\n /* sizing */\n --pwc-modal-max-width: 720px;\n --pwc-modal-width: 92vw;\n\n /* spacing */\n --pwc-modal-padding-header: 12px 16px;\n --pwc-modal-padding-body: 16px;\n --pwc-modal-padding-footer: 12px 16px;\n --pwc-modal-gap-footer: 8px;\n\n /* visuals */\n --pwc-modal-bg: #fff;\n --pwc-modal-backdrop: rgba(0, 0, 0, 0.45);\n --pwc-modal-border-radius: 6px;\n --pwc-modal-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);\n --pwc-modal-separator: rgba(0, 0, 0, 0.08);\n\n /* controls */\n --pwc-modal-close-radius: 4px;\n --pwc-modal-close-hover-bg: rgba(0, 0, 0, 0.06);\n}\n\npwc-modal-dialog dialog.pwc-modal-dialog {\n border: none;\n padding: 0;\n max-width: min(var(--pwc-modal-max-width), var(--pwc-modal-width));\n width: var(--pwc-modal-width);\n}\n\npwc-modal-dialog dialog.pwc-modal-dialog::backdrop {\n background: var(--pwc-modal-backdrop);\n}\n\npwc-modal-dialog .pwc-modal-dialog-surface {\n background: var(--pwc-modal-bg);\n border-radius: var(--pwc-modal-border-radius);\n box-shadow: var(--pwc-modal-shadow);\n overflow: hidden;\n}\n\n/* Header */\n\npwc-modal-dialog .pwc-modal-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--pwc-modal-padding-header);\n border-bottom: 1px solid var(--pwc-modal-separator);\n}\n\npwc-modal-dialog .pwc-modal-dialog-title {\n margin: 0;\n font-size: 1.1rem;\n font-weight: 600;\n}\n\n/* Close button */\n\npwc-modal-dialog .pwc-modal-dialog-x {\n appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-size: 1.25rem;\n line-height: 1;\n padding: 4px 6px;\n cursor: pointer;\n border-radius: var(--pwc-modal-close-radius);\n}\n\npwc-modal-dialog .pwc-modal-dialog-x:hover {\n background: var(--pwc-modal-close-hover-bg);\n}\n\n/* Body */\n\npwc-modal-dialog .pwc-modal-dialog-body {\n padding: var(--pwc-modal-padding-body);\n}\n\n/* Footer */\n\npwc-modal-dialog .pwc-modal-dialog-footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--pwc-modal-gap-footer);\n padding: var(--pwc-modal-padding-footer);\n border-top: 1px solid var(--pwc-modal-separator);\n}\n\npwc-modal-dialog .pwc-modal-dialog-close {\n appearance: none;\n border: 1px solid rgba(0, 0, 0, 0.25);\n background: transparent;\n padding: 6px 12px;\n border-radius: var(--pwc-modal-close-radius);\n cursor: pointer;\n}\n\npwc-modal-dialog .pwc-modal-dialog-close:hover {\n background: var(--pwc-modal-close-hover-bg);\n}";
242
+
243
+ // src/modal-dialog/index.js
244
+ function register() {
245
+ PwcModalDialog.registerCss(modal_dialog_default);
246
+ define();
247
+ }
248
+ register();
249
+ export {
250
+ register
251
+ };